feat(security): ADR-0090 D10 — delegate action capabilities to MCP agents#2845
Merged
Conversation
…ents An MCP agent may now invoke the business ACTIONS its delegating user can run, gated by the `actions:execute` scope. Previously an agent principal carried no system capabilities, so any capability-gated action was denied even when the user was entitled to it. resolve-execution-context now keeps the delegating user's systemPermissions on the agent context ONLY when the token carries `actions:execute` (else none — and the MCP tool surface already hides the action tools). That scope is the user's explicit consent to let the agent act for them, so the action capability gate (actionPermissionError) is delegated accordingly. Never widens the agent's DATA reach: what an action reads/writes still flows through the object CRUD/FLS/RLS ceiling ∩ user intersection (a data:read agent invoking a writing action is still blocked at the write; even a data:write agent can't touch better-auth-managed tables; cap-gated OBJECT access stays denied since that gate uses the resolved ceiling sets, which carry no caps). Residual = a cap-gated action with a purely external effect (email/webhook), exactly what actions:execute consents to. Tests: producer unit (agent + actions:execute → user caps; without → none) + MCP run_action bridge integration (agent with delegated cap runs the gated action; without, denied). Full runtime suite 495 green; tsc clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 15 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
added a commit
that referenced
this pull request
Jul 12, 2026
) The agent docs described MCP OAuth as "runs as you / scopes narrow tool families" — inaccurate after the ADR-0090 D10 agent work shipped (#2838/#2843/#2845). An OAuth client is now an agent acting ON BEHALF OF the user, bounded by the INTERSECTION of the consent scopes and the user's own permissions/RLS, and scopes are a real data-layer ceiling (a data:read token can never write, even where the user could) — not just a tool-family filter. - packages/mcp/src/skill.ts (SKILL.md served to every connecting agent): fix "runs as you" → agent-on-behalf + scope-ceiling intersection. - content/docs/ai/agents.mdx: same correction, user-facing. - docs/design/permission-model.md: mark the overlap rule *enforced* (#2838/#2843/#2845); honestly mark the three still-PLANNED agent guardrails — the grant-ceiling *lint* (runtime intersection already caps it; the bind-time lint is #2849), destructive-action co-sign, and the double-signature audit provenance (writer stamps only the delegator today). - docs/adr/0090: status note that D10 evaluation semantics landed. mcp suite 66 green. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Follow-up to the D10 agent principal (#2843). An MCP agent may now invoke the business actions its delegating user can run, gated by the
actions:executescope.Before this, an agent principal carried no system capabilities (
systemPermissions = []), so any capability-gated action (requiredPermissions) was denied even when the user was fully entitled to it — the most likely friction for a real agent integration, sincerun_actionis a headline MCP tool.How
resolve-execution-contextkeeps the delegating user'ssystemPermissionson the agent context only when the token carriesactions:execute(otherwise none — and the MCP tool surface already hides the action tools without that scope). Theactions:executescope is the user's explicit consent to let the agent act on their behalf, so the capability gate (actionPermissionError, which readsctx.systemPermissions) is delegated accordingly. The gate itself is unchanged and identity-agnostic.Why this is safe (does not widen data reach)
The action-invocation gate is a door separate from the object CRUD/FLS/RLS intersection, which is driven by the resolved ceiling sets (they carry no capabilities). So:
object ceiling ∩ user. Adata:readagent that invokes a writing action is still blocked at the write.data:writeagent cannot touch better-auth-managed identity tables (the ceiling denies them).ctx.systemPermissions).delete_user(the user holdsmanage_org_users), but the action's internalsys_userdelete is blocked by the agent's ceiling → the action fails. The data ceiling backstops every action that touches platform data.The only residual is a capability-gated action whose effect is purely external (send email, call webhook) — exactly what
actions:executeconsents to. Tighter per-action agent scoping is the per-client-grants follow-up.Tests
resolve-execution-context): agent withactions:executeinherits the user'ssystemPermissions(but its DATA ceiling stays the scope-derived set); agent without it holds none.run_actionMCP bridge): an agent that inherited the capability runs the gated action; an agent that didn't is denied withrequires capability.tscclean.🤖 Generated with Claude Code