Skip to content

Commit c438fdf

Browse files
committed
fix(webapp): scope sessions auth per-task, not wildcard
Devin caught two real issues with the previous resource shape on PR #3474: 1. The "body isn't available at auth-resolve time" claim was wrong. Action-route resource callbacks receive the parsed body as the 4th arg (apiBuilder.server.ts:710). Other routes like api.v1.tasks.batch.ts use it (line 33). 2. The auth check is OR across resource types — listing both `sessions: "*"` and `tasks: body.taskIdentifier` would let a `write:sessions`-only JWT pass for any task, defeating the per-task narrowing. Replace `() => ({ sessions: "*" })` with `(_, __, ___, body) => ({ tasks: body.taskIdentifier })` and rely on the `write:sessions` / `admin` super-scopes for broad access. A JWT scoped only to `write:tasks:foo` can now only create sessions whose taskIdentifier is `foo`. MCP-style flows that hold `write:sessions` continue to work via the super-scope path. Verified: MCP `start_agent_chat` → `send_agent_message` → `close_agent_chat` still passes locally; webapp typecheck clean.
1 parent 51b0fcd commit c438fdf

1 file changed

Lines changed: 16 additions & 8 deletions

File tree

apps/webapp/app/routes/api.v1.sessions.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,23 @@ const { action } = createActionApiRoute(
110110
// behalf of the developer without weakening the browser model.
111111
allowJWT: true,
112112
authorization: {
113-
// Resource scoping by `taskIdentifier` isn't possible at auth-resolve
114-
// time — action routes don't pass `body` to the resource callback,
115-
// and the task name only lives in the body. We require a `sessions`
116-
// resource scope (wildcard) and rely on `write:sessions` / `admin`
117-
// super-scopes to gate access. Per-task narrowing happens implicitly
118-
// because the JWT-issuer (e.g. cli-v3 MCP) decides which scopes to
119-
// request when minting the token.
113+
// Per-task scoping via `body.taskIdentifier` (action-route resource
114+
// callbacks receive the parsed body as the 4th arg — see
115+
// `apiBuilder.server.ts:710`). A JWT scoped only to `write:tasks:foo`
116+
// can only create sessions whose `taskIdentifier` is `"foo"`. Broad
117+
// callers (cli-v3 MCP, customer servers wrapping their own auth)
118+
// hold the `write:sessions` super-scope and bypass the per-task
119+
// check entirely.
120+
//
121+
// Note: the auth check is OR across resource types, so listing both
122+
// `sessions` and `tasks` here would let a `write:sessions`-scoped
123+
// JWT pass for *any* task — defeating the per-task narrowing. Keep
124+
// it task-only and let the super-scope path handle session-level
125+
// wildcard access.
120126
action: "write",
121-
resource: () => ({ sessions: "*" }),
127+
resource: (_params, _searchParams, _headers, body) => ({
128+
tasks: body.taskIdentifier,
129+
}),
122130
superScopes: ["write:sessions", "admin"],
123131
},
124132
corsStrategy: "all",

0 commit comments

Comments
 (0)