← All articles

Multi-User ComfyUI: How to Keep Agents From Seeing Each Other's Work

Updated

Three brass keys lying on cracked dark slate, green light glowing up from under them.

A password on your generation server decides who gets in, not what each caller sees once inside. Separating two agents means the server itself has to stamp an owner on every job and filter every read by it — and most setups check the key only when work is submitted, which leaves everything readable.

Point two AI agents at the same self-hosted ComfyUI and they share everything on it: the same gallery, the same job history, the same output files. That’s fine until one of them is doing work the other shouldn’t see — a client’s project, another person’s images, anything you’d rather keep apart. This is what drawing that line actually takes, and where the obvious approaches stop short.

What a login gives you, and what it doesn’t

ComfyUI has a --multi-user mode, and it is not an access control system. It reads the active user from a comfy-user header the client supplies, with no token or signature behind it, and GET /users lists every registered user ID to anyone who asks. Any client on the network can read that list, put someone else’s ID in the header, and read, overwrite or delete their saved workflows. That’s the problem statement of an open RFC filed against ComfyUI in August 2026, which proposes giving profiles a real bearer token.1

The official route for API access is comfy-api-proxy, a service that serves the Comfy API v2 contract in front of a local install. Its authentication is off by default, and when you turn it on it is one static bearer token for the whole server.2 That suits what it’s for — it binds to loopback, and the token exists so you can safely expose it. But a single shared token gives every caller the same identity, which is the same as no identity once you have three agents.

The community extensions — Sentinel, comfyui-login, MSS-Login, ComfyUI-MultiUser — put a login in front of the server: JWT sessions, API tokens, in some cases per-user input and output folders.3 They solve a real problem, which is a ComfyUI exposed to the internet with no password at all. They are still doors. Once through, most of the API is one shared room.

The RFC names the limit exactly: an external reverse proxy “only protect[s] the entire server globally; [it] cannot enforce internal per-user directory isolation.”1 Nothing you put in front of a service can scope what that service returns. The service has to do it.

This is a different problem from the one most agent-security writing covers. That work is about credentials pointing outward — an agent holding a key to someone else’s SaaS, where the argument is that a static key “authenticates the application, not the user,” so every request looks identical to whoever receives it.4 Here the direction is inward. You own the server and you issued the keys; the question is what your own API hands back to each caller who presents one.

Why the key usually gets checked in only one place

There’s a failure mode specific to generation servers, and it’s worth naming because it looks like a finished feature. FlixML shipped it, so what follows describes our own code before September 2026.

The keys existed. Each agent had one, stored as a hash, restricted to certain characters and workflows, capped on concurrent jobs. All of it was enforced at exactly one point: job submission.

Every endpoint that read something ignored the key. The gallery listing returned all media, the jobs list returned all jobs, and character records, projects and every file under /media/* went to anyone who could reach the port. An agent restricted to one character couldn’t generate anyone else’s images and could still download all of them. A revoked key was treated as no key, so revoking access granted anonymous access.

Writes are where permission feels like it matters. Submitting a job costs GPU time and occupies hardware, so the check gets written there first and the feature looks done. Reading a file costs nothing, so nothing guards it, and the data leaves through the read path.

What a key has to do to scope reads

Four pieces. Skipping any one leaves the data readable. What follows is the shape FlixML settled on.5

Ownership written down at creation. Every job, image, upload and project stores the ID of the key that made it. Without a stamp on the row there’s nothing to filter by later, and no middleware can reconstruct it after the fact. Derived work inherits: a render belongs to whoever owns the project it came from, not to whoever pressed go.

One choke point rather than a check per route. Resolve the caller and enforce scope in a single application-wide dependency instead of decorating each endpoint. The reason is maintenance. A per-route check protects the routes that existed the day it was written, and the next endpoint added under /api/projects/{project_id}/… is unprotected by default while looking fine in review. A central check keys off the path parameters — project_id, character_id, prompt_id — so a new route under an existing parameter is scoped the moment it exists.

The read paths that get forgotten. Static file serving, thumbnails, and any passthrough to the engine underneath. FlixML proxies part of ComfyUI’s own API, where history, queue, prompt and view each expose every caller’s prompts and outputs, so those are admin-only; the endpoints that merely describe the install — system_stats, object_info, models, features — stay open. A file request is the easiest to miss, because it never passes through your API layer at all.

Admin as a flag on the key. Somebody has to see everything: you, looking at your own server. Make that a property of a key instead of a separate login system, and the owner’s view becomes the same code path as everyone else’s with the filter switched off.

Why the browser needs different treatment

This constraint shapes the whole design, and it isn’t obvious until the UI goes blank.

Every API call from a frontend can carry Authorization: Bearer <key>. An <img> or <video> tag cannot. The browser fetches those URLs itself, with no header you control, so a gallery of thumbnails arrives as a stream of anonymous requests. Scope the file route and your own UI shows broken images.

The fix is a session. The frontend posts the key once to an endpoint that stores it in an HttpOnly cookie, and the authorization code reads the key from either the header or that cookie. Agents keep using the header, browsers get the cookie, and media requests are scoped like everything else. It’s also why requiring keys should default to off: a fresh install with no keys yet would otherwise lock the owner out on first boot.

Three things that break on a server with history

Existing rows have no owner. We turned this on against a database of roughly 1,300 jobs and 1,200 media files. Exactly one job carried an owner ID; the media had none at all. Everything else would have gone invisible to everyone the moment filtering went live. How you backfill is a judgment call about your own data — we assigned media by the character it depicted and left the remainder to admin only — but it has to be a deliberate migration step, not a discovery afterward.

A key can lock itself out. Rotating your own key invalidates the session you’re holding. Deleting it, revoking it, or removing your own admin flag does the same, permanently, if it was the last admin key. FlixML refuses all four when the target is the calling key. You can always mint a new key from the command line, but the UI shouldn’t offer a button that ends your own access.

An unknown key must never fall back to anonymous. If missing keys are allowed, it’s tempting to treat a bad key the same way. Then revocation stops working, because a revoked key is a bad key. Missing and invalid are different states: one can be permitted, the other is always a 401.

One hole survives all of this, and it’s worth stating. A file already sitting in a ComfyUI node’s input folder can be named directly in a workflow parameter by a caller who knows the filename, bypassing the ownership check on that path. Scoping covers what the wrapper catalogs, not files placed into ComfyUI by other means.

Setting it up on your own install

Work in this order, because each step depends on the one before it.

  1. Stamp ownership first, and backfill what’s already there. Filtering against unowned rows hides everything.
  2. Create an admin key for yourself and sign in before you require keys, or you’ll lock yourself out of your own UI.
  3. Turn the requirement on, then check the paths that don’t go through your API: file serving, thumbnails, and any engine passthrough.
  4. Give each agent its own key, scoped to what it needs. One key per agent rather than per machine, so revoking one doesn’t disturb the others.

What this gets you is a boundary between agents you run, so their work stops pooling. It is not a security boundary against a hostile caller already on your network, and it doesn’t make the server multi-tenant the way a hosted product means it. For a private box with several agents on it, the first thing is usually what you actually wanted.

For why the API is shaped for agents in the first place, see agent-driven ComfyUI. For the FlixML commands and config, see API keys and access.

Sources

Footnotes

  1. ComfyUI issue #15822, filed 23 August 2026: in --multi-user mode, GET /users lists all user IDs unauthenticated, and get_request_user_id() trusts the client-supplied comfy-user header with no token or signature, so any client can read, overwrite or delete another profile’s /userdata/*. Also the source of the quoted limitation of external reverse proxies. Open as of September 2026. 2

  2. API Proxy for Self-Hosted ComfyUI, ComfyUI documentation: authentication is not required by default, and a configured proxy uses a single static bearer token passed as the SDK API key.

  3. ComfyUI-Sentinel (deprecated; login, multi-user registration, IP filtering, per-user input/output directories), ComfyUI-MultiUser (username/password login with JWT sessions, API tokens, permissions) and MSS-Login (JWT sessions, long-lived API tokens, role-based permissions) — extensions that add authentication to a ComfyUI install.

  4. OAuth vs API Keys for AI Agents, Scalekit, March 2026: the outbound case — a static key “authenticates the application, not the user,” and “every request appears identical to the API.” Representative of the agent-credential literature, which covers agents calling third-party services rather than servers scoping their own callers.

  5. FlixML implementation, September 2026: app/flixml/auth.py (the authorize() dependency, owner scoping, the admin flag and the ComfyUI passthrough allowlist), POST /api/session for the HttpOnly cookie, and scripts/manage_agent_keys.py for the first admin key. Row counts are from our own install at migration time.