Skip to content

conductor /subagent and /approval unauthenticated on the default launch; wildcard CORS exposes them to any visited web page #726

Description

@EvolveAegis

What

frontends/conductor.py adds a RemoteAuth middleware (lines 124-135, commit 6bed654), but the gate at line 130 is a 3-way AND — if args.key and remote not in ("127.0.0.1", "::1") and not secrets.compare_digest(got, want) — so it only fires when --key is set AND the caller is off-loopback AND the header mismatches. Two holes fall out of that: loopback callers are always exempt regardless of --key, and --key has no default (line 33), so python frontends/conductor.py starts on 127.0.0.1:8900 with no auth on any route. The same app installs CORSMiddleware(allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) at line 122, so a cross-origin fetch() from any page the user visits passes preflight and reaches POST /subagent (591), POST /subagent/{sid}?action=input (597), POST /approval (632), GET /subagent (576).

How to reproduce

# Terminal 1 — default launch (loopback, no --key)
python frontends/conductor.py --no-browser &

# Terminal 2 — no Authorization header; this is what a drive-by fetch() delivers
NONCE=$(date +%s)
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:8900/subagent \
  -H 'Content-Type: application/json' \
  -d "{\"prompt\":\"canary-$NONCE: reply OK and stop\"}"
# actual: 200   (under any auth this should be 401)

curl -s http://127.0.0.1:8900/subagent | grep -o "canary-$NONCE[^\"]*"
# actual: canary-<NONCE>: reply OK and stop

The browser form is fetch("http://127.0.0.1:8900/subagent",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({prompt:"..."})}) — the wildcard CORS response satisfies the preflight and the POST is delivered.

Impact / scope

A visited web page (or any local process) can start a subagent with an attacker-authored prompt and feed it follow-up instructions via action=input, while the agent runs under the user's privileges and keys; POST /approval pushes a fake approval card to the front-end. This is cross-principal task dispatch and instruction injection into the user's agent session, not remote code execution — the agent's terminal action is model-mediated and not demonstrated end-to-end. Direct remote reach is bounded by the loopback bind; the wildcard CORS is what extends it to "any page open in a browser while the conductor runs." PR #650 raised the same loopback + CORS-* + unauth-route pattern on desktop_bridge.py's /path/open; it was closed with no maintainer response, and the conductor routes weren't covered.

Suggested change

Have RemoteAuth enforce for loopback too, default --key to a generated token written to a file (or refuse to start without one), and replace allow_origins=["*"] with an explicit origin allow-list. Happy to open a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions