What
The invariant that author: "user" is "minted only by the viewer composer (genuine human keystrokes)" is enforced only in the MCP tier, not in REST or the stores.
POST /api/comments takes author straight from the body and stores it verbatim: server/app.ts:907 → sqlStore.ts:442 / storage.ts:412.
- MCP coerces it (
server/mcpHttp.ts:154 rejects named === "user"). REST does not.
- The sandboxed
send-prompt bridge correctly stamps author:"surface" (viewer/src/App.tsx:412), so this is not an agent-XSS vector.
Impact
Anyone with write access (the single deploy token, or anyone reachable on a no-token local board) can:
curl -X POST /api/comments -d "{\"surface\":\"<id>\",\"text\":\"...\",\"author\":\"user\"}"
…and inject authoritative-seeming commands into the agent's feedback stream, since collectFeedback (server/app.ts:348) delivers author === "user" comments as userFeedback, and wait_for_feedback (author=user) reads the same stream. The agent treats them as genuine human instructions. AGENTS.md flags this feedback channel as the thing to guard hardest.
Fix options
- Reject
author:"user" on REST; have the composer send a viewer-only marker the server validates.
- Stop claiming "user" means "human" given no per-user auth, and treat it as "token-holder".
- Add a separate human-keystroke channel distinct from the REST write path.
Severity: Medium
What
The invariant that
author: "user"is "minted only by the viewer composer (genuine human keystrokes)" is enforced only in the MCP tier, not in REST or the stores.POST /api/commentstakesauthorstraight from the body and stores it verbatim:server/app.ts:907→sqlStore.ts:442/storage.ts:412.server/mcpHttp.ts:154rejectsnamed === "user"). REST does not.send-promptbridge correctly stampsauthor:"surface"(viewer/src/App.tsx:412), so this is not an agent-XSS vector.Impact
Anyone with write access (the single deploy token, or anyone reachable on a no-token local board) can:
curl -X POST /api/comments -d "{\"surface\":\"<id>\",\"text\":\"...\",\"author\":\"user\"}"…and inject authoritative-seeming commands into the agent's feedback stream, since
collectFeedback(server/app.ts:348) deliversauthor === "user"comments asuserFeedback, andwait_for_feedback(author=user) reads the same stream. The agent treats them as genuine human instructions. AGENTS.md flags this feedback channel as the thing to guard hardest.Fix options
author:"user"on REST; have the composer send a viewer-only marker the server validates.Severity: Medium