fix(multiuser): redact other users' current-item identifiers from queue status events#135
Open
fix(multiuser): redact other users' current-item identifiers from queue status events#135
Conversation
…ue status events
QueueItemStatusChangedEvent embeds the SessionQueueStatus, which includes the
currently-running item's item_id, session_id, and batch_id. The event ships to
user:{owner} and admin rooms. When user A's item changed status while user B's
item was the one in progress, owner A's frontend received the event with B's
identifiers exposed.
In _set_queue_item_status, scrub item_id/session_id/batch_id from the embedded
queue_status when the in-progress item belongs to a different user than the
changed item. Aggregate counts remain global (not user-sensitive).
Identified out-of-scope in the security audit of #127.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e-ai#8971) * feat: Add virtual boards that dynamically group images by date Virtual boards are computed on-the-fly via backend queries, not stored in the database. The first virtual board type groups images by creation date into sub-boards per day. The feature is togglable via the board settings popover and the collapse state persists across sessions. * add missing Redux state migration * Chore ruff check * docs(gallery): document virtual boards by date Add a "Virtual Boards by Date" section to the gallery feature docs explaining how to enable the new By Date section, what each entry shows, and that virtual boards are a read-only view over existing images. * fix(ui): invalidate VirtualBoards tag on image generation Optimistic updates in onInvocationComplete cover ImageList and BoardImagesTotal but not VirtualBoards, so date-grouped counts and cover thumbnails would only refresh on the next mutation. Trigger an explicit invalidation when at least one non-intermediate image was added.
…es (invoke-ai#8899) * Add LLM-powered prompt expansion and image-to-prompt features Adds two new buttons to the positive prompt area: - "Expand Prompt" uses a local TextLLM model (AutoModelForCausalLM) to expand brief prompts into detailed image generation prompts - "Image to Prompt" uses an existing LLaVA OneVision model to generate descriptive prompts from uploaded images Backend: new TextLLM model type with config, loader, pipeline wrapper, workflow node, and two new API endpoints (expand-prompt, image-to-prompt). Also fixes HuggingFace metadata fetch assertion error when file size is None. Frontend: ExpandPromptButton and ImageToPromptButton components with model picker popovers, RTK Query mutations, and model type hooks. Buttons only appear when compatible models are installed. * chore fix windows paths * Fix device mismatch for LLM inference and add CPU-only toggle for Text LLM models Derive the execution device from the loaded model parameters instead of the global TorchDevice chooser so that cpu_only models no longer receive GPU-bound inputs. Also expose the existing cpu_only setting in the frontend Model Manager for Text LLM models. * Harden LLM endpoints and add tests - Bound max_tokens to 1-2048 on ExpandPromptRequest to prevent OOM - Replace asserts with explicit type checks and proper HTTP status codes (404 for unknown models, 422 for wrong model type, 500 for unexpected) - Use float32 dtype for cpu_only TextLLM models instead of global fp16 - Add 16 tests for TextLLMPipeline and API request validation * Add Ctrl+Z undo for LLM prompt changes Saves the previous prompt before LLM overwrites it (Expand Prompt and Image to Prompt). Pressing Ctrl+Z in the prompt textarea restores the original prompt. Undo state auto-expires after 30 seconds and is cleared when the user types manually. * Add documentation and What's New entry for LLM prompt tools - Add docs/features/prompt-tools.md covering Expand Prompt, Image to Prompt, compatible models, Ctrl+Z undo, and the workflow node - Register new doc page in mkdocs.yml under Features - Add What's New item in en.json for the LLM Prompt Tools feature * fix: resolve merge conflict in mkdocs.yml nav * feat(ui): allow dragging gallery images onto prompt box for Image to Prompt Add drop target on the positive prompt textarea so users can drag images from the gallery directly into the prompt area. When dropped, the Image to Prompt popover opens automatically with the image pre-loaded, ready for description generation. * chore typegen * Fix typo in Z-Image Turbo diversity description * Fix three bugs in LLM/VLM utility endpoints Move torch.no_grad() from async endpoint into worker functions where inference actually runs, since the context manager does not carry across the thread boundary used by asyncio.to_thread(). Add threading.Lock around load_model() calls to serialize access to the thread-unsafe model loader, preventing race conditions from concurrent HTTP requests. Catch ImageFileNotFoundException in image_to_prompt and return 404 instead of letting it fall through to the blanket 500 handler. * Fix tokenizer validation, drag-drop dead end, and i18n for LLM prompt tools Validate tokenizer files at model probe time instead of deferring to runtime. Guard image drag-drop on the prompt textarea behind LLaVA model availability. Add missing modelManager.textLLM i18n key and replace all hardcoded strings in ImageToPromptButton and ExpandPromptButton with translation calls. * Add unit tests for promptUndo module * Fix typo in Z-Image Turbo diversity description * Chore fix typegen --------- Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
…metadata recall (invoke-ai#8995) * Fix(Flux2): Correct guidance_embed, add guidance support for Klein 9B Base, and fix metadata recall Klein 4B and 9B (distilled) have guidance_embeds=False, while Klein 9B Base (undistilled) has guidance_embeds=True. This commit: - Sets guidance_embed=False for Klein 4B/9B and adds Klein9BBase with True - Adds guidance parameter to Flux2DenoiseInvocation (used by Klein 9B Base) - Passes real guidance value instead of hardcoded 1.0 in flux2/denoise.py - Hides guidance slider for distilled Klein models, shows it for Klein 9B Base - Shows Flux scheduler dropdown for all Flux2 Klein models - Passes scheduler to Flux2 denoise node and saves it in metadata - Adds KleinVAEModel and KleinQwen3EncoderModel to recall parameters panel * test(flux2): cover Klein guidance gating, scheduler metadata, and recall dedupe Add a mock-based harness for buildFLUXGraph that locks in the FLUX.2 orchestration: guidance is written to metadata and the flux2_denoise node only for klein_9b_base, distilled variants (klein_9b, klein_4b) omit it, the FLUX scheduler is persisted into both metadata and the denoise node, and separately selected Klein VAE / Qwen3 encoder land in metadata. Add parsing tests for the metadata recall handlers: KleinVAEModel and KleinQwen3EncoderModel only fire when the current main model is FLUX.2, and the generic VAEModel handler now bails out for flux2 / z-image so the metadata viewer no longer renders duplicate VAE rows next to the dedicated Klein / Z-Image handlers. * Chore pnpm fix * Update version to 1.5.0 in flux2_denoise.py * Update condition for rendering ParamFluxScheduler * feat(flux2): add Klein4BBase variant for FLUX.2 Klein Base 4B models Recognize FLUX.2-klein-base-4B on import via filename heuristic. The variant shares Klein4B's architecture (Qwen3-4B encoder, context_in_dim=7680) and reports guidance_embeds=False in its HF config, consistent with Klein 9B Base. UI behavior stays identical to distilled Klein4B until CFG support is wired up in a follow-up. * Change Wrong Comment * refactor(flux2): remove inert guidance UI/metadata for FLUX.2 Klein All current FLUX.2 Klein variants (4B, 4B Base, 9B, 9B Base) report guidance_embeds=false in their HF transformer config (or have zeroed projection weights), so the guidance scalar has no effect on output. The linear UI previously exposed a guidance slider for klein_9b_base and wrote the value into metadata, which misled users into thinking it was steering generation. * Chore typegen * fix test * fix(flux2): skip Guidance metadata recall for legacy FLUX.2 images The generic Guidance metadata handler unconditionally parsed `metadata.guidance` and dispatched `setGuidance(value)` into the shared params slice. For images generated before the Klein guidance cleanup, this still fired — silently writing a stale guidance value into the global state, which then leaked back into FLUX.1 on model switch. Gate the handler on `metadata.model.base`: reject parsing when the image was generated with a FLUX.2 model. The handler is then skipped for both display and recall on legacy FLUX.2 metadata, matching the "silently ignored" contract stated in the PR. - parsing.tsx: check metadata.model.base in Guidance.parse() - parsing.test.tsx: three new cases covering FLUX.2 gating, FLUX.1 pass-through, and back-compat for metadata without a model field --------- Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com> Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
…me (invoke-ai#9079) * Fix lazy If branch pruning and skipped-parent handling in graph runtime * Tighten lazy If runtime edge-case handling * Polish lazy If runtime diagnostics and idempotency --------- Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
) * feat(ui): add canvas snapshot save/restore functionality Add ability to save and restore canvas state snapshots, allowing users to preserve their canvas layout at any point and restore it later. This is useful when the canvas freezes or resets unexpectedly. Backend: - Add get_keys_by_prefix and delete_by_key to client_state persistence - Add corresponding API endpoints Frontend: - Add canvasSnapshotRestored reducer to canvasSlice - Add useCanvasSnapshots hook for snapshot CRUD operations - Add CanvasToolbarSnapshotMenuButton with save/restore UI - Add i18n keys for snapshot feature - Regenerate API schema types Tests: - Add tests for new client_state endpoints (prefix search, key deletion) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ui): address review feedback for canvas snapshot feature - Preserve current modelBase on snapshot restore to prevent bbox desync with the active model (mirrors resetState pattern) - Exclude snapshot restore from undo history so it cannot be accidentally undone - Migrate manual fetch calls to RTKQ endpoints (clientState.ts) so snapshots go through the shared API transport layer with proper auth, session-expiry handling and sliding-window token refresh - Validate referenced images on restore and warn when some are missing - Detect incompatible (schema-changed) snapshots and show a specific error message instead of a generic failure toast - Disable snapshot restore while the canvas is staging to prevent entity ID conflicts with in-progress generations - Sort snapshot list by updated_at instead of rowid so re-saved snapshots appear at the top - Add pre-flight backend reachability check before image validation to avoid false "missing images" warnings when offline Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(ui): consolidate collectImageNames to shared canvasProjectFile utility Remove the local collectImageNames from useCanvasSnapshots and reuse the shared, more comprehensive version from canvasProjectFile.ts that was introduced by the canvas project save/load feature (invoke-ai#8917). Snapshots don't include global ref images, so an empty array is passed for that parameter. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(canvas-snapshots): escape LIKE wildcards, warn on overwrite, fix default name chars - Escape %, _, \ in client_state prefix query to prevent accidental wildcard matching - Confirm before overwriting an existing snapshot instead of silently replacing it - Use - instead of / and : in the default snapshot name to avoid key separator clashes * fix(canvas): align canvasProjectRecalled with snapshot restore pattern Preserve modelBase, call syncScaledSize, and exclude from undo history to avoid bbox/model desync on project load — same pattern already used by canvasSnapshotRestored. --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
…voke-ai#9045) * feat(recall): support model-free reference images in recall API The recall parameters API previously exposed only `loras`, `control_layers`, and `ip_adapters`. This meant reference images used by architectures that feed images directly into the main model — FLUX.2 Klein, FLUX Kontext, and Qwen Image Edit — could not be sent through the recall endpoint at all: they have no adapter model to resolve, so they could not ride in the `ip_adapters` list. This change adds a new `reference_images` field on RecallParameter that carries only an `image_name`. The backend validates the file exists in outputs/images and forwards the resolved metadata (width/height) in the broadcast event. The frontend's recall handler picks the right config type (`flux2_reference_image` / `flux_kontext_reference_image` / `ip_adapter` fallback) via getDefaultRefImageConfig() based on the currently-selected main model, matching the behavior of a manual drag-and-drop, and dispatches `refImagesRecalled` with replace:false so these append rather than clobber any adapters already applied in the same event. Also consolidates the two existing docs under docs/contributing/RECALL_PARAMETERS/ (RECALL_PARAMETERS_API.md and RECALL_API_LORAS_CONTROLNETS_IMAGES.md) into a single RECALL_PARAMETERS_API.md that documents the full request schema including the new field. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(recall): cover loras, control layers, and ip_adapters paths The original recall_parameters router (PR invoke-ai#8758) shipped without any unit tests for its three collection fields. This commit backfills that coverage alongside the reference_images tests added in the previous commit. The resolver helpers (resolve_model_name_to_key, load_image_file, process_controlnet_image) are monkey-patched via module-level attribute replacement so each test can pin down a specific resolution outcome without spinning up the model manager or an image-files service. Two small factory helpers (make_name_to_key_stub / make_load_image_file_stub) make that ergonomic. New coverage: * LoRAs — multi-entry resolution + weight/is_enabled pass-through, silent drop on unresolvable names, is_enabled default of True. * Control layers — ControlNet resolution precedence, fall-through to T2I Adapter and Control LoRA in order, missing image gracefully warned-and-continued, processed_image attached when the processor returns data, unresolvable entries dropped. * IP Adapters — IPAdapter-before-FluxRedux lookup order, method / image_influence pass-through, missing image gracefully warned-and- continued, unresolvable entries dropped. * Combined happy path — full request with prompts + model + all four collection fields, verifying every resolved value reaches the broadcast payload. * Main-model drop — an unresolvable main model is scrubbed from the broadcast so the frontend never receives a stale model name. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(frontend): typegen * chore: fix lint errors and typegen * fix(test): patch ApiDependencies in auth_dependencies to fix recall tests The patched_dependencies fixture only monkeypatched ApiDependencies in the recall_parameters module, but the endpoint also resolves CurrentUserOrDefault via auth_dependencies, which accesses ApiDependencies.invoker independently. Patch both import sites. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(frontend): typegen * feat(recall): add strict mode to clear unset parameters on recall Add a `strict` query parameter (default false) to POST recall endpoint. When true, parameters not in the request body are reset: list fields (loras, control_layers, ip_adapters, reference_images) become [] and scalar fields become null, so the frontend clears stale state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(frontend): eliminate ref image doubling from race between Promise.all chains IP adapters and model-free reference images were dispatched via two independent Promise.all chains — one with replace:true, the other with replace:false. When a previous recall's promises were still in-flight they could resolve after the clear and re-append stale entries, doubling the list. Combine both into a single Promise.all with one replace:true dispatch so the race is impossible. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
…voke-ai#8930) Co-authored-by: Alexander Eichhorn <alex@eichhorn.dev>
* feat: add Custom Node Manager for installing and managing node packs from the UI
Adds a new "Nodes" tab (circuit icon) to the sidebar with a two-panel layout:
- Left: list of installed custom node packs with reload and uninstall
- Right: tabbed install UI (Git URL / Scan Folder) with install log
Backend API endpoints (POST install, DELETE uninstall, POST reload, GET list)
handle git clone, pip dependency install, runtime node loading/unloading,
and automatic workflow import from node pack repositories. Workflows are
tagged with node-pack:<name> and removed on uninstall.
Includes user and developer documentation, plus 31 tests (21 backend, 10 frontend).
* Chore typegen
* Chore Typegen without Node
* feat(custom-nodes): detect deps instead of auto-installing
Auto-running pip on requirements.txt could pull incompatible
packages into the running InvokeAI env and break the app. The
installer now detects requirements.txt or pyproject.toml,
returns requires_dependencies + dependency_file, and the UI
shows a persistent warning toast pointing the user to the
node pack's documentation.
* Chore ruff
* custom nodes: require admin auth, share imported workflows, and localize UI
- Gate install/uninstall/reload routes on AdminUserOrDefault so they respect multiuser auth
- Import pack workflows under the installing admin with is_public=True so all users see them
- Replace hardcoded English strings in CustomNodesList and CustomNodesInstallLog with translations
- Reuse existing common/queue keys for clear/status, drop duplicates in en.json
* test(custom_nodes): update _import_workflows_from_pack tests for owner_user_id
Pass owner_user_id="admin" in all call sites and assert that user_id and
is_public=True are forwarded to workflow_records.create().
* custom nodes: track imported workflows via manifest and harden pack listing
- Record imported workflow IDs in .invokeai_pack_manifest.json inside the pack
directory; uninstall reads the manifest before rmtree and deletes only those
IDs, so user-authored workflows sharing the pack tag are preserved
- Gate GET /v2/custom_nodes/ with AdminUserOrDefault to match install/uninstall
/reload and prevent unauthenticated disclosure of absolute node pack paths
- Extract getParentDirectory() helper that handles both POSIX and Windows
separators so the nodes-directory label renders on all platforms
- Add regression tests for manifest roundtrip, colliding-tag preservation, and
parent-directory extraction across separator styles
* Chore typegen
* ui: hide Custom Nodes tab for non-admin users in multiuser mode
Add useIsCustomNodesEnabled hook (mirrors useIsModelManagerEnabled) and
conditionally render the tab in VerticalNavBar. Backend routes already
reject non-admin callers; this prevents the UI from advertising controls
that would 403.
* ui: guard Custom Nodes tab content for non-admin persisted state and add auth regression tests
- Suppress CustomNodesTabAutoLayout render and redirect to generate via
navigationApi.switchToTab when a non-admin user lands on a persisted
customNodes tab
- Add TestCustomNodesAuthorization with 10 route-level tests verifying
unauthenticated (401), non-admin (403), and admin (200) for list,
install, uninstall, and reload endpoints
- Add decision-matrix test for useIsCustomNodesEnabled covering
single-user, multiuser admin, multiuser non-admin, and unloaded user
* test: add shared helper for custom-nodes gate + admin happy-path tests
Extract getIsCustomNodesEnabled so test imports the real logic
instead of a local reimplementation. Add install/uninstall
admin-success tests with mocked filesystem/subprocess.
* fix(custom-nodes): return optimistic default while setup status loads
Prevents redirect away from persisted customNodes tab on startup
in single-user mode when RTK Query hasn't resolved yet.
* ui: split custom nodes permission into isKnown/isAllowed to close loading window
useIsCustomNodesEnabled now returns { isKnown, isAllowed } so the navbar
hides the tab conservatively (isAllowed=false while loading) while the
redirect only fires once the decision is definitive (isKnown && !isAllowed),
preventing both the non-admin flash and the single-user kickout.
* refactor(custom-nodes): extract permission derivation into shared helper
Tests now import deriveCustomNodesPermission directly instead
of mirroring the hook logic in a local simulateHook, so hook
and tests can never drift.
* Chore Knit fix
* fix(custom-nodes): purge full module subtree on uninstall
Only removing sys.modules[pack_name] left submodules cached, so
reinstall reused them and the @invocation decorators never re-
registered the nodes — the pack loaded with 0 nodes until a
full process restart. Now _purge_pack_modules strips the root
and every pack_name.* key.
* fix(custom-nodes): purge full module subtree on uninstall
Only removing sys.modules[pack_name] left submodules cached, so
reinstall reused them and the @invocation decorators never re-
registered the nodes — the pack loaded with 0 nodes until a
full process restart. Now _purge_pack_modules strips the root
and every pack_name.* key.
---------
Co-authored-by: Jonathan <34005131+JPPhoto@users.noreply.github.com>
Co-authored-by: Lincoln Stein <lincoln.stein@gmail.com>
* feat: add editable source_url field to model config
Allow users to store a URL (e.g. download page or model page) on any
model, independent of the original installation source. The URL is
editable in the model edit form and displayed as a clickable link in
the model header view.
* fix(mm): validate source_url to prevent XSS via javascript: URI scheme
* fix(mm): harden source_url validator against non-string inputs
Guard the pydantic `mode="before"` validator with an isinstance check
so PATCH bodies like `{"source_url": 123}` surface as a 422 instead of
a 500 from AttributeError on `.startswith()`. Also set `type="url"` on
the model edit input for basic browser-level validation.
* Chore Typegen
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
QueueItemStatusChangedEventembeds aSessionQueueStatusthat includes thecurrently-running item's
item_id,session_id, andbatch_id. The full eventships to
user:{owner}andadminrooms. When user A's item changed status whileuser B's item was the one in progress, owner A's frontend received the event with
B's identifiers exposed in the embedded
queue_status.This was identified as out-of-scope in the security audit on #127.
Fix
In
_set_queue_item_status, after buildingqueue_status, scrubitem_id/session_id/batch_idwhen the in-progress item belongs to a different userthan the changed item. Aggregate counts stay global (not user-sensitive). The
frontend never reads those fields off the event payload (only
batch_status.batch_id,which is the changed item's own batch — no leak), so no UI behavior changes.
Test plan
test_event_redacts_other_users_current_item_identifiersverifies that when user B's item is in_progress and user A's item is canceled,
A's emitted event has
queue_status.item_id/session_id/batch_id == None.test_event_preserves_owner_current_item_identifiersconfirms no over-redactionwhen there's no in-progress item.
test_event_preserves_identifiers_when_current_item_is_the_changed_itemconfirmsidentifiers ARE exposed when the in-progress item is the changed item itself.
🤖 Generated with Claude Code