Skip to content

feat(security): ADR-0090 D10 — delegate action capabilities to MCP agents#2845

Merged
os-zhuang merged 1 commit into
mainfrom
feat/d10-agent-action-caps
Jul 11, 2026
Merged

feat(security): ADR-0090 D10 — delegate action capabilities to MCP agents#2845
os-zhuang merged 1 commit into
mainfrom
feat/d10-agent-action-caps

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

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:execute scope.

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, since run_action is a headline MCP tool.

How

resolve-execution-context keeps the delegating user's systemPermissions on the agent context only when the token carries actions:execute (otherwise none — and the MCP tool surface already hides the action tools without that scope). The actions:execute scope is the user's explicit consent to let the agent act on their behalf, so the capability gate (actionPermissionError, which reads ctx.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:

  • Whatever an action reads or writes still flows through object ceiling ∩ user. A data:read agent that invokes a writing action is still blocked at the write.
  • Even a data:write agent cannot touch better-auth-managed identity tables (the ceiling denies them).
  • Capability-gated object access stays denied to the agent (that gate uses the ceiling sets, not ctx.systemPermissions).
  • Concretely: an agent for an org-admin can invoke delete_user (the user holds manage_org_users), but the action's internal sys_user delete 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:execute consents to. Tighter per-action agent scoping is the per-client-grants follow-up.

Tests

  • Producer unit (resolve-execution-context): agent with actions:execute inherits the user's systemPermissions (but its DATA ceiling stays the scope-derived set); agent without it holds none.
  • Integration (run_action MCP bridge): an agent that inherited the capability runs the gated action; an agent that didn't is denied with requires capability.
  • Full runtime suite 495 green; tsc clean.

🤖 Generated with Claude Code

…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>
@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 11, 2026 3:13pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/runtime.

15 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/index.mdx (via @objectstack/runtime)
  • content/docs/api/wire-format.mdx (via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx (via packages/runtime)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/runtime)
  • content/docs/deployment/index.mdx (via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx (via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx (via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx (via @objectstack/runtime)
  • content/docs/permissions/authentication.mdx (via @objectstack/runtime)
  • content/docs/plugins/packages.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectos/http-protocol.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/runtime)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/runtime)
  • content/docs/releases/implementation-status.mdx (via @objectstack/runtime)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang merged commit 261aff5 into main Jul 11, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the feat/d10-agent-action-caps branch July 11, 2026 15:42
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant