Commit Graph

146 Commits

Author SHA1 Message Date
benjcooley fd3bea9156 refactor: replace typed vendor domains with a named integrations dispatch
Eleven vendor-shaped Protocols are replaced by one IntegrationsDomain with
`call(integration, operation, **params)` and `describe()`. The published API
was growing by one class per third-party service while the enforcement point
— `integrations.<name>`, checked at the broker — never varied. The hand-kept
public stub had already fallen behind that growth: Luma, ImgBB and SenseNova
existed in the implementation but had never been published.

Three defects surfaced and are fixed here:

- LlamaCppModelRef.generate reached the vendor by attribute, so every pack
  using the returned handle broke at the wire, not at the call site.
- The in-process path exposed vendors as public attributes and had no
  `call`, so a node could work unsandboxed and fail once sandboxed. The
  vendors are now private and reached only by name, giving both paths the
  same surface.
- `describe` was declared sync in the Protocol but implemented async in the
  guest, which must round-trip to the host.

Also removes a test for load_onnx_image_classifier, deleted from core in
2076700f without its tests; the surviving _validate_onnx_weight_file test
is kept.
2026-09-03 16:40:13 -07:00
benjcooley 0dee840a53 fix: satisfy ruff across the V2 SDK surface 2026-09-02 11:24:55 -07:00
benjcooley 21f3a1cad1 merge: origin/master into v2-nodes-api 2026-09-02 11:22:46 -07:00
Alexander Piskun ec803fc97e [Partner Nodes] feat(Minimax-H3): add the Max model to the Reference node (#16041)
* [Partner Nodes] feat(MiniMax): add H3 Max option to H3 reference-to-video node

Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
2026-09-02 19:29:30 +04:00
Terry Jia ace9172e95 Annotate temp paths in Preview3DAdvanced / PreviewGaussianSplat / PreviewPointCloud outputs (#16038) 2026-09-02 07:35:27 -04:00
benjcooley e7f7524ec7 feat: let any handle reach the operation vocabulary
op() was defined only on ImageRef, so a pack holding any other handle had
no way to call a named operation on it and had to wait for core to grow a
typed method. The dispatch itself was always generic: the broker's wire
parameter is named image but passes straight through to
ops.apply(op, subject, params), and core's own vendor wrappers already send
non-image handles along it.

Moving op() to the base Ref is what lets a pack build its own typed
accessor over a capability core knows nothing about, so the operation
vocabulary can keep growing while the API does not. ImageRef keeps its
override to narrow the return type.
2026-09-01 18:29:32 -07:00
Purz 624f56565b [Partner Nodes] feat(MiniMax): add H3 Max option to H3 text-to-video and first-last-frame nodes (#16025)
* [Partner Nodes] feat(MiniMax): add H3 Max option to H3 text-to-video and first-last-frame nodes

Signed-off-by: Alexander Piskun <bigcat88@icloud.com>

* [Partner Nodes] fix(downscale): never make a downscaled image more elongated than its source

Signed-off-by: Alexander Piskun <bigcat88@icloud.com>

* [Partner Nodes] fix(M3-Max): add check fpr max prompt length

Signed-off-by: Alexander Piskun <bigcat88@icloud.com>

* [Partner Nodes] chore(Minimax): correct route name

Signed-off-by: bigcat88 <bigcat88@icloud.com>

---------

Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
Signed-off-by: bigcat88 <bigcat88@icloud.com>
Co-authored-by: Alexander Piskun <13381981+bigcat88@users.noreply.github.com>
Co-authored-by: Alexander Piskun <bigcat88@icloud.com>
2026-09-01 20:58:21 -04:00
benjcooley 7a941e6e8b feat: add a versioned, self-describing capability registry
Ports de91e80c from api-v2-runtime so both cores expose the same mechanism
for describing and negotiating the operation vocabulary.
2026-09-01 16:01:57 -07:00
benjcooley f23c8a57c2 refactor: extract bounded weight cache and fix LRU eviction
Ports 31d76988 from api-v2-runtime so both cores carry the same cache
behaviour. Seven of the thirteen copy-pasted caches evicted with
dict.popitem(), dropping the most recently inserted entry rather than the
least recently used, which pinned whichever model loaded first.
2026-09-01 16:01:57 -07:00
T8star-Aix 3216c62e99 Support SenseNova U1.5 (CORE-411) (#15922) 2026-08-31 20:05:53 -07:00
benjcooley 6e42b35e78 Clamp system metrics to safe integers 2026-08-31 10:48:41 -07:00
benjcooley ec955d53ba Expand secure V2 SDK surface 2026-08-31 10:42:20 -07:00
Terry Jia f938505952 feat: add VideoTrim and VideoCrop nodes with VIDEO_EDIT widget inputs (#15637) 2026-08-30 15:54:48 -07:00
Alexander Piskun 77739723a3 fix(memory): respect the container cgroup memory limit instead of host RAM (#15927) 2026-08-27 07:57:31 -07:00
Alexander Piskun d8e7bbc9d5 fix(video): remux HEVC to mp4/mov as hvc1 via hevc_mp4toannexb (#15809) 2026-08-26 20:28:50 -04:00
comfyanonymous 5653b4ac8e Support avif in Save Image Advanced node. (#15891) 2026-08-26 19:17:57 -04:00
Constantine 5f0c4e18cb Fix default database path for custom user directory (#14539)
* fix: respect user directory for default database

* refactor: use None default for --database-url instead of argv scan

Per review: default --database-url to None and treat a non-None value
as explicit at resolution time. Removes the sys.argv scan and the
database_url_explicit attribute. database_default_path now serves
directly as the legacy copy source. Adds regression tests for the
unchanged no-flag default path and explicit URLs at the legacy
location.

* refactor: rename legacy database to .bak after copy

Per review: after copying the legacy install-dir database to the
effective user directory, rename the original to comfyui.db.bak so a
later launch without --user-directory cannot silently fall back to a
diverged copy, while keeping the file around for recovery. Also hoist
the database_default_path import to module scope.

* fix: guard legacy migration on existing .bak and rename before copy

Per review: bail out of the legacy migration when comfyui.db.bak
already exists, so only the first run migrates and later launches with
a fresh --user-directory cannot grab a database another instance is
using. Rename before copy so os.replace fails fast if the legacy DB is
held open by a running instance.

---------

Co-authored-by: guill <jacob.e.segal@gmail.com>
2026-08-24 16:19:03 -07:00
comfyanonymous 9db05e0e1f Add colorspace option and change bit_depth to a combo on CreateVideo. (#15810) 2026-08-22 21:07:29 -04:00
benjcooley 3deeb22ed2 fix(sdk): unwrap_outputs must preserve ui/expand/block_execution
`unwrap_outputs` rebuilds a node's NodeOutput in order to swap output refs back
for real objects. It rebuilt it from the results alone — `NodeOutput(*resolved)`
— silently discarding `ui`, `expand` and `block_execution`.

The practical effect: no SDK_REFS node could be an output node. ComfyUI only
emits the `executed` websocket event, the one that delivers a node's results to
the frontend, for nodes that return ui data (`if len(output_ui) > 0`). So a
converted PreviewImage-style node executed perfectly and then displayed
nothing, with no error anywhere to explain it. `expand` (subgraph expansion)
and `block_execution` were lost the same way.

Resolving refs is a transport concern and has no business changing what the
node said. Generic fix, not specific to any backend: it applies equally to the
in-process path.
2026-08-22 00:27:56 -07:00
benjcooley e7160bf7ab feat(sdk): execution seam carries the work unit — plan.inputs/node_module, dispatch(plan, local_call, runtime)
Generic out-of-process enablement: the ExecutionPlan now ships the node's
module spec and ref-wrapped inputs, and dispatch receives the per-node host
runtime (refs/ctx/ops) so an external backend can execute the node elsewhere
and broker guest calls against the same ref table. In-process default ignores
all of it; legacy nodes unaffected (seam tests green).
2026-08-21 20:41:28 -07:00
benjcooley f1681ee669 feat(sdk): ops-first asset interface — OpsProvider seam, SDK_REFS marshaling, raw() escape hatch
Nodes operate on assets (image.invert()) and never receive buffers; compute
runs on the trusted plane via the OpsProvider seam. Raw buffer access becomes
a permissioned, discouraged escape hatch (raw(); forces dedicated tier under
the overlay). The execution seam wraps heavy inputs as refs for SDK_REFS
nodes and resolves output refs for downstream legacy nodes. The .pyi contract
no longer imports torch.

POC stand-ins (interface debt, ledgered in the overlay repo DEBT.md):
invert/scale enumerated on OpsProvider; duck-typed wrap_inputs; SDK_REFS
class-attr opt-in.
2026-08-21 20:36:37 -07:00
benjcooley e92fa15805 feat(sdk): wire execution seam + .pyi contract + POC node & test
- execution.py: route V3 node dispatch through providers.execution_backend
  with a behavior-preserving local_call closure (exact sync/async-task
  semantics retained); bind per-node ctx+refs inside the invocation scope so
  the concurrent-async path is correct. V1 nodes untouched. Default backend =
  in-process => byte-identical to today.
- comfy_api/latest/_sdk_public.pyi: authoritative type contract for the secure
  SDK (backend analog of the frontend comfy-api.d.ts): refs, ctx + domains,
  ctx() accessor; host/overlay seam separated.
- custom_nodes/comfy_sdk_poc: SandboxInvert POC node authored to the SDK.
- tests-unit: seam regression (sync+async SDK nodes through the real engine;
  provider-swap intercept). Verified PASS.
2026-08-21 20:07:27 -07:00
Jukka Seppänen 0e65cb9071 feat: Support Pixal3d and TRELLIS2 (CORE-278) (CORE-199) (CORE-236) (CORE-312) (#14718) 2026-08-21 20:32:25 -04:00
comfyanonymous 76135e557d Add HDR color space options to h264 codec in Save Video node. (#15764) 2026-08-20 20:16:31 -04:00
comfyanonymous dcbcf8c2e1 Support HDR video saving, AV1 codec, mkv and webm. (#15741) 2026-08-20 19:29:20 -04:00
Simon Pinfold 3aba3daef3 Derive asset preview URLs from the file path (#15509)
preview_url was assembled from a /api/view link whose type was chosen by
matching the asset's tags against "input" then "output". Anything written
anywhere else - temp above all, where preview nodes put their images - fell
off the end of that chain and came back with no preview at all. Tags are
user-editable, so removing one also silently destroyed the URL.

Derive the URL from where the file actually sits instead. That covers every
root /api/view serves, temp included, and no longer depends on tags or on a
filename in user_metadata. A file outside those roots, or content no client
can render from its own bytes, gets no preview URL rather than one that
cannot work.

Nominated previews are resolved a page at a time rather than per row, so a
list costs one extra query however long it is. A preview that is soft-deleted
or not visible to the caller drops out of that lookup and is no longer
advertised.

Co-authored-by: guill <jacob.e.segal@gmail.com>
2026-08-18 21:00:28 -07:00
Simon Pinfold cc0fc21fea Keep temp-directory assets visible while their files exist (#15510)
* Keep temp-directory assets visible while their files exist

Assets written to the temp directory were flagged as missing and dropped
from GET /api/assets, even with the file sitting on disk. One list of
directories was answering two different questions -- where the scanner
looks for new files, and which files ComfyUI considers its own -- and
temp belongs only in the second, so every temp reference was disowned by
the prune that runs at startup and on POST /api/assets/prune.

Ownership now covers temp. Discovery still does not: the temp directory
is wiped before the scan runs, and assets written there are already
registered with a hash, mime type and dimensions, so walking it would
find nothing. Temp references are instead reconciled against the
filesystem directly, so a temp file that really is gone is still retired
rather than lingering as a broken entry.

get_prefixes_for_root becomes get_scan_prefixes_for_root so the two
questions are told apart by name rather than by comment.

* Cover the unhashed temp asset in the reconciliation tests

The existing temp tests all registered hashed assets, so they never
exercised the path an unhashed asset takes when its file is gone: the
orphaned rows are removed rather than kept as missing, exactly as under
any other root.
2026-08-17 22:57:55 -07:00
Christian Byrne 0d80858061 Forward node class attributes into schema for dataset nodes (#15683) 2026-08-16 23:19:53 -07:00
Alexander Piskun 37ac9ff44f fix(tests): accept Python 3.14 math error messages (#15645)
Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
2026-08-15 22:43:20 +04:00
Christian Byrne a9ab2b62da [Partner Nodes] Stop adding an opaque alpha channel to API node images (#15369)
* Stop adding an opaque alpha channel to API node images

bytesio_to_image_tensor converted every downloaded image to RGBA, so nodes
whose API returns no transparency still emitted a 4 channel IMAGE. Keep the
alpha when the decoded image has one, stay RGB when it does not.

---------

Signed-off-by: bigcat88 <bigcat88@icloud.com>
Co-authored-by: bigcat88 <bigcat88@icloud.com>
2026-08-15 21:27:24 +04:00
Alexander Brown addd479729 Fix Generate Text ignoring thinking=false on Gemma4 E2B/E4B (#15278) 2026-08-12 22:44:14 -04:00
Christian Byrne 26d7f85568 Fix PreviewAny escaping non-ASCII text in dict and list previews (#15513) 2026-08-11 23:42:35 -07:00
chelsealong 6233790c6d Fix VAEDecodeTiled crash on NestedTensor latents (MiniMax H3) (#15477)
VAEDecode unwraps a NestedTensor latent (video/audio pair) to its
video component before calling vae.decode(). VAEDecodeTiled skipped
this unwrap and passed the NestedTensor straight into
vae.decode_tiled(), which fails deep in the MiniMax H3 video VAE when
a real tensor's .to() is called with the NestedTensor as an argument.

Fixes #15468.
2026-08-10 23:00:23 -04:00
Simon Pinfold 34744cd29e Add tags_all / tags_any / tags_none tag filters to the assets list API (#15332)
* Implement tags_all/tags_any/tags_none on the assets list API (BE-6600)

Adds the three canonically-named tag filter params to GET /api/assets and
GET /api/assets/tags/refine:

- tags_all: asset carries every tag (replaces include_tags)
- tags_any: asset carries at least one tag (new)
- tags_none: asset carries no tag (replaces exclude_tags)

Clauses intersect; tags_none always wins. include_tags/exclude_tags remain
as permanent deprecated aliases and behave exactly as before when used on
their own.

Invalid combinations return 400 INVALID_TAG_FILTER, but only when the
request uses at least one new-name parameter (non-empty after
normalisation):

- mixed spellings of one slot (include_tags with tags_all, exclude_tags
  with tags_none)
- the same tag in the effective all-list and none-list (query can never
  match)

Old-names-only requests gain no new error paths: include_tags=a&exclude_tags=a
still returns an empty 200. tags_any/tags_none overlap stays valid (dead
term, not a dead query).

* Address review findings: positional-compat, deprecation metadata, test matrix

- Move any_tags to the end of the four touched signatures: inserting it
  mid-signature silently misbound pre-existing positional callers (e.g.
  a caller passing name_contains positionally would have it consumed as
  any_tags).
- Mark include_tags/exclude_tags Field(deprecated=True) on both list
  schemas so generated schema metadata matches the contract, not just a
  comment (schemas_out.py already uses this form for Asset.name).
- Add tests: legal cross-slot old/new combinations, repeated query-key
  concatenation (pins Core behavior; outside the cross-platform
  contract), tags_any two-page cursor consistency (total/has_more/
  no-overlap), refine-route mixed-spelling rejection + legacy-conflict
  preservation, and schema deprecation metadata.

* Pin tag-value opacity: case-sensitive matching, byte-exact conflict check

The prod tag survey (~/comfy/prod-model-tag-shape.md) found live
case-distinct tag pairs (SEEDVR2/seedvr2) that resolve differently, so
the contract now states tag values are opaque byte-strings. Pin that:
case-distinct tags filter separately, and a case-distinct all/none pair
is not an INVALID_TAG_FILTER conflict.

* Document tags_all/tags_any/tags_none in openapi.yaml, deprecate aliases

Add the three tag-filter parameters to both listAssets and
getAssetTagHistogram parameter blocks and mark include_tags/exclude_tags
deprecated: true, keeping the spec in step with the runtime schemas so
generated clients can discover the new filters while the aliases stay
present for existing consumers.

* Move schemas_in import to module scope in test_list_filter

Review feedback: no import cycle requires the local import.

* Silence per-request DeprecationWarning in the tag-filter remap shim

Reading the deprecated include_tags/exclude_tags fields by attribute
fires pydantic's DeprecationWarning on every list/refine request even
for callers using only the new names. The warning is aimed at API
clients, not the server's own remap; read via model_dump instead.

* Cap tag-filter lists at 100 entries, all spellings

Review finding: unbounded tag lists fan out into one correlated EXISTS
per tag on both page and count statements. Cap each list at 100
normalized entries with 400 INVALID_TAG_FILTER naming the parameter.

Applies to the legacy spellings as well — a deliberate, decided
exception to the old-names-behave-identically rule, since a cap only on
new names would leave the same fan-out reachable through the aliases.

* Strip process narration from comments

Comments carried decision dates, contract cross-references, and review
context. Keep only the constraints the code cannot show, one line each.
2026-08-10 14:05:21 -07:00
Terry Jia 8fadc7b5be [Partner Nodes] feat: ImageCompositor node with layer-state compositing, layer from bbox and Seedream Layer Separation node (#15317) 2026-08-07 13:25:37 -07:00
comfyanonymous 2881e61610 Store mp4 metadata at the beginning of the file when possible. (#15195) 2026-08-01 03:21:28 -04:00
comfyanonymous 235b466a0c Add crf option to save video node. (#15191) 2026-08-01 00:27:48 -04:00
Alexander Piskun 6cedd34343 [Partner Nodes] fix(ByteDance): encode stereo reference audio without doubling its duration (#15177)
* [Partner Nodes] fix(ByteDance): encode stereo reference audio without doubling its duration

Signed-off-by: bigcat88 <bigcat88@icloud.com>
2026-07-31 21:57:45 +03:00
Matt Miller 9cf91339b7 Fix SVG previews broken by the stored-XSS forced-download (#15149)
* Fix SVG previews broken by the stored-XSS forced-download

/view and the assets download route force every SVG to
application/octet-stream + attachment. That blocks the stored XSS from
GHSA-779p-m5rp-r4h4, but it also breaks the SVG node output and Media
Assets previews, which request the file with a plain <img>.

Exempt only that case. An SVG referenced by an <img> loads in secure
static mode with scripting and external references disabled, so the
payload cannot fire. The attack needs the SVG to become a document,
which arrives with a different Sec-Fetch-Dest. Browsers set that header
themselves and page script cannot override it. A missing header, from a
non-browser client or a proxy that strips it, fails closed.

The blocklist itself is unchanged; this is a call-site gate.

* Don't let a cache replay the inline SVG into document context

The Sec-Fetch-Dest exemption makes /view and the assets content route vary
their Content-Type and Content-Disposition on a request header, but neither
response said so. FileResponse emits Last-Modified/ETag and the cache_control
middleware skips /view (the filename is in the query string, not the path), so
the inline image/svg+xml variant is heuristically cacheable. A cache keyed on
the URL alone could hand an entry primed by an <img> load to a later top-level
navigation of the same URL, turning the SVG back into a document and
re-enabling the stored XSS the forced download blocks.

Set Vary: Sec-Fetch-Dest and Cache-Control: no-store on both branches, not just
the exempt one: a cached attachment replayed to an <img> would re-break the
preview this fix exists to restore.

Also strip parameters from content_type before building the assets response.
mime_type there is uploader-supplied and unvalidated, and aiohttp rejects a
charset in the content_type argument with ValueError, so a stored
"image/svg+xml; charset=utf-8" turned a valid inline SVG into a 500.

Route-level guards now pin the headers on both branches and the parameterised
mime type; all three fail against the previous commit.
2026-07-29 23:29:36 -07:00
彼彼 03978e1e81 [feat]Add JoyImageEdit native model support (#14428) 2026-07-15 23:48:28 -04:00
Alexander Piskun cc6b352511 fix(Video): stream the video transcode instead of buffering every frame in RAM (CORE-353) (CORE-351) (#14813) 2026-07-15 15:23:43 +08:00
comfyanonymous 917faef771 Support PID 1.5 models. (#14894) 2026-07-12 09:43:30 -07:00
comfyanonymous 92ddf07ba1 Try to fix some issues with the seedvr VAE. (#14877) 2026-07-10 19:54:28 -04:00
John Pollock 8e2e54e2b8 Add SeedVR2 support (CORE-6) (#14424) 2026-07-10 15:07:42 +08:00
comfyanonymous 73e84d5ec8 Support convrot int4 models. (#14859)
linear_dtype in comfy_quant metadata can be used to set if the int4 op does
the matrix multiplication in int8 or int4, the default is int4 on GPUs that
support it with fallback to int8 for GPUs that don't.
2026-07-09 18:57:09 -04:00
Simon Pinfold 412aaab0e2 feat(api): expose registered extension filters on /experiment/models (#14797)
Each folder in the listing now carries its registered extension
allowlist verbatim; an empty array means the folder accepts any
extension (match-all), mirroring filter_files_extensions semantics.

Gives consumers the filtering rule itself rather than just its output:
/models/{folder} lists files by the per-folder rule but the rule is not
exposed anywhere, and /experiment/models/{folder} filters everything by
the global supported_pt_extensions regardless of registration.
Presentation-level filtering of match-all folders (e.g. hiding
README/config noise that repository-downloading custom nodes leave in
model directories) is deliberately left to the consumer.

Co-authored-by: guill <jacob.e.segal@gmail.com>
2026-07-09 12:59:30 -07:00
Alexander Piskun b35819712e feat: allow --comfy-api-base target ephemeral testenvs (#14569)
* feat: allow --comfy-api-base target ephemeral testenvs

Signed-off-by: bigcat88 <bigcat88@icloud.com>

* refactor: name /features data as backend flags, not frontend

---------

Signed-off-by: bigcat88 <bigcat88@icloud.com>
Co-authored-by: guill <jacob.e.segal@gmail.com>
2026-07-08 23:20:10 -07:00
Simon Pinfold 55a15f87ce feat(assets): add namespaced model_type tags and align tag semantics (#14511)
* feat(assets): add namespaced model type tags

* fix(assets): mark path-derived upload tags automatic

* fix(assets): merge duplicate scan specs

* test(assets): make duplicate path normalization portable

* feat(assets): add loader_path as the authoritative loader locator (#14796)

* fix(assets): filter model_type tags by bucket extension sets

Buckets sharing a base directory (e.g. diffusion_models and a custom
unet_gguf) tagged every file in the directory regardless of whether the
bucket could load it, so .safetensors files were tagged
model_type:unet_gguf and vice versa. Carry each bucket's registered
extension set through get_comfy_models_folders and only emit a
model_type tag when the file extension matches, keeping the empty-set
match-all convention from folder_paths.filter_files_extensions.

Files under a model base matching no bucket now keep only the models
tag instead of every directory-matching model_type tag.

* feat(assets): replace response file_path with persisted loader_path

The old file_path response field was a namespaced storage locator
(models/checkpoints/foo.safetensors): not an absolute path, not unique
identity, and not the value a loader consumes. Nothing needs that shape
on the wire (hash/ID-based locating is the long-term direction), so it
is dropped rather than renamed; the storage-root matching stays internal,
powering display_name.

What loaders DO need is the in-root loader path (category dropped:
models/checkpoints/foo/bar.safetensors -> foo/bar.safetensors). Serve it
as a first-class loader_path field, persisted on asset_references
(migration 0006) and written by every ingest pipeline at insert, so
responses read the column verbatim.

Like the model_type tags, loader_path is a seed-time derivative of the
model folder registry, maintained by the same scan lifecycle (new files seed
fresh values, pruning retires rows whose bucket disappeared). Rows
predating the column serve a null loader_path; databases from before
this stack already need recreating for the base branch's tag changes.

loader_path resolves every registered base including extra_model_paths
entries; display_name only the canonical storage roots. A file can
therefore be loadable with no display name (extra-path models) or the
reverse (unregistered files under the models root), and loader_path is
null exactly when no loader can resolve the file.

* test(assets): lock loader_path matrix (asymmetry, null, persist/read)

Cover the behaviour that has no production change but is easy to regress:
the extra-path asymmetry (loadable but no storage namespace), null
loader_path persistence for orphan files, and the response reading the
stored column with a compute fallback for un-backfilled rows.

* fix(assets): persist subfolder-qualified loader_path for ingested outputs

ingest_existing_file built its seed spec with the file's basename, so
outputs saved into a subfolder persisted loader_path (and the
user_metadata filename that preview URLs split for their subfolder
param) as just the basename: the served locator pointed at a file that
does not exist at that path. Scanner and seeder specs already derive
fname via compute_loader_path; use the same derivation here.

* fix(assets): only extension-matching buckets contribute a loader_path

The model-base match in get_asset_category_and_relative_path ignored
each bucket's extension set, so a file inside a registered base whose
extension the bucket cannot load (e.g. a .txt uploaded into
model_type:checkpoints) advertised a loader_path that no loader list
would ever resolve, while the tag side of the same stack already
excluded it. Apply the extension check used for backend tags (empty set
accepts any extension), keeping loader_path null exactly when no loader
can resolve the file.

* fix(assets): refresh loader_path when re-ingesting an existing reference

upsert_reference only wrote loader_path on the INSERT branch, so
re-ingesting an existing reference (an output overwritten in place, or a
file re-registered after its loader_path derivation changed) kept the
stale or NULL value forever. Write it on the UPDATE branch too, with a
null-safe change guard so a loader_path difference alone is enough to
trigger the update, and identical values stay a no-op.

* fix(assets): repair semantic merge breakage from #14796 and master

Two textually-clean but semantically-broken merges:

- routes.py lost its folder_paths import when #14796's import block
  superseded the base's, while the content-type hardening added via the
  base's master merge still calls folder_paths.is_dangerous_content_type.
- master's SVG download-hardening test uploads with the pre-namespacing
  bare checkpoints tag, which this branch's destination validation
  rejects; use model_type:checkpoints.

---------

Co-authored-by: guill <jacob.e.segal@gmail.com>
2026-07-08 22:00:08 -07:00
Silver 091b70edda add models-directory launch argument (#9113) 2026-07-08 22:20:47 +08:00
Matt Miller 96e0e3585b security: fix four vulnerabilities (GHSA-779p-m5rp-r4h4) (#14734)
* security: fix five vulnerabilities (GHSA-779p-m5rp-r4h4)

- CVE-2026-56670: force download of SVG/XML responses on /view to prevent stored XSS
- CVE-2026-56671: contain /experiment/models/preview reads within the model folder
- CVE-2026-56672: stop inline rendering of uploaded /userdata/{file} content
- CVE-2026-56673: prevent path traversal in get_annotated_filepath (LoadImage /prompt input)
- CVE-2026-56674: reject opaque/null Origin to close the CSRF middleware bypass

Adds regression tests under tests-unit/security_test/ covering all five.

* security: address review feedback on GHSA-779p fixes

- Fix Windows CI failure in test_get_annotated_filepath: compare against
  os.path.abspath(...) to match the intentional abspath normalization added
  by the traversal hardening (abspath prepends the drive letter on Windows).
- origin_check: narrow the bare `except:` in is_loopback() to ValueError so
  genuine interrupts aren't swallowed (review nit).
- origin_check: guard .port access in is_cross_origin_forbidden() so a
  malformed/out-of-range port (e.g. Origin: http://127.0.0.1:99999) fails
  closed with a 403 instead of surfacing an uncaught 500 in the middleware.
- server /view: escape backslash/quote in the Content-Disposition filename
  (RFC 6266 quoted-string) so a filename containing a double quote can't
  malform the response header.

* security: address CodeRabbit review feedback on GHSA-779p tests

- test #3: guard the symlink-escape test with a try/except skip so it no
  longer errors on Windows CI where os.symlink needs elevated privileges /
  Developer Mode (mirrors the guard in the sibling test #2).
- test #5: refresh the stale module docstring to describe the actual /view
  gating (view_image closure calling folder_paths.is_dangerous_content_type,
  the normalising check) instead of the bypassable raw set-membership test.

* revert(security): drop CVE-2026-56674 Origin: null CSRF change

Per maintainer review, the reported CSRF is already mitigated by the pre-existing
Sec-Fetch-Site: cross-site check for current browsers, and the null-origin
rejection risked breaking legitimate sandboxed-iframe embeds. Restores
origin_only_middleware and is_loopback in server.py to their prior state
(the Sec-Fetch-Site check is retained) and removes utils/origin_check.py and its
regression test. The other four GHSA-779p fixes are unaffected.
2026-07-02 20:44:54 -07:00