Files
kandev/apps/backend/internal/auth
João Salavisa 103878047c fix(auth): scope in-session agent MCP calls to the task owner (#1937)
* fix(auth): scope in-session agent MCP calls to the task owner

The MCP tools an agent gets automatically inside its own session are
relayed over the agent's WebSocket stream, which carries no credential.
The backend dispatched them on the raw stream context, so the task
service saw no identity — its signal for an internal caller (event bus,
pollers, office schedulers) — and served the request unscoped. An agent
that supplied another user's task_id or workflow_id was given their
data, violating the rule that knowing an ID must not grant access.

Resolve the owning user from the stream's own AgentExecution (task ->
workspace -> owner) and attach that real stored identity before
dispatch, so the existing authorize* checks apply exactly as they do on
the PAT-authenticated /mcp endpoint. The owning task never comes from
the agent-supplied payload: honoring a payload session_id would let an
agent name another user's session and inherit their identity.

Tool handlers are unchanged. Auth disabled stays unscoped, unclaimed
pre-auth workspaces stay visible to everyone, and a lookup failure
denies the dispatch rather than falling back to full access.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(auth): never leave in-session MCP dispatch unscoped

Review follow-up. Three paths still returned an identity-free context
under enforced auth, and "no identity" is exactly what the task service
reads as an internal caller and serves unscoped:

- Unowned workspace. CreateWorkspace only stamps an owner for scoped
  callers, so internal callers keep producing owner_id='' rows after
  setup. Those streams got full cross-user access. Scope them to a
  sentinel user ID no account can hold: the service's "empty owner_id or
  my own ID" rule then limits them to the unowned rows that are public
  by the compatibility contract, and nothing else.
- Task with no workspace. Same treatment, same reasoning.
- Missing task or workspace row. A lookup that reports not-found as
  (nil, nil) fell through to unscoped; treat it as a resolution failure.

Also deny, rather than fabricate a member identity, when the owner's
account is gone or disabled. Disabling a user revokes their sessions and
PATs, so a still-running agent session was the one remaining way into
their workspace.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 09:56:47 +01:00
..