feat(security): ADR-0090 D10 — activate agent principal (OAuth → scope ceiling)#2843
Merged
Merged
Conversation
…e ceiling) Wires the PRODUCER side of the D10 intersection that shipped in #2838, so it stops being dormant. An MCP request authenticated with an OAuth access token is now resolved as an AI agent acting ON BEHALF OF the human `sub`, and its effective permission is the intersection of a scope-derived capability ceiling AND the user's own grants. - resolve-execution-context (producer): a verified MCP OAuth token that names an authorized client (`azp`) → principalKind:'agent', onBehalfOf:{userId}, and the agent's OWN grants become the scope-derived ceiling (data:read→read-only, data:write→CRUD, neither→no data). userId stays the human (owner/RLS scope); user systemPermissions cleared so a cap-gated action can't ride the user's capabilities. No client → human. - plugin-security: three built-in ceiling sets (mcp_agent_data_read / _write / _restricted) — pure CRUD bits, NO row-level security (all row/owner/tenant narrowing comes from the delegating user on the other side of the intersection). An agent skips the additive human baseline (member_default) and its fallback is the restricted (no-object-access) set, so a mis-resolved agent fails CLOSED, never open. - spec: MCP_AGENT_PERMISSION_SET_* + scopesToAgentPermissionSets(), single-sourced beside the OAuth scope constants; api-surface regenerated. Behaviour change (a security tightening): an MCP OAuth request previously executed with the FULL authority of the logged-in user (scopes narrowed only the tool surface). Now the scope is also a real data-layer ceiling — a data:read token can never write ANY record, even via a crafted call, regardless of what the user could do. Strictly consistent with "a scope can never grant more than the user could do" (the intersection only narrows), closing the confused/compromised-agent gap. Tests: producer mapping unit-tested in runtime (resolve-execution-context, 491 green incl. mcp-oauth); enforcement dogfooded against the served engine (showcase-agent-scope-ceiling — data:read reads but cannot write/create; data:write for the same user can). plugin-security 298, plugin-sharing 76, D10/OWD/permission dogfood unchanged. api-surface check + 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 4 package(s): 108 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Jul 11, 2026
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
Wires the producer side of the ADR-0090 D10 agent intersection that shipped in #2838, so it stops being dormant. An MCP request authenticated with an OAuth access token is now resolved as an AI agent acting on behalf of the human
sub, and its effective permission is the intersection of a scope-derived capability ceiling and the user's own grants.Before this,
principalKind:'agent'/onBehalfOfwere a P1 context shape that nothing ever produced — the D10 enforcement engine had no input. #2698 already captured the OAuthclientId(azp) but discarded it; this translates it.How
resolve-execution-context(producer) — when a verified MCP OAuth token names an authorized client (azp), the request resolves toprincipalKind:'agent'withonBehalfOf:{ userId }(the human). The agent's OWN grants are replaced by the scope-derived ceiling:data:read→ read-only,data:write→ full CRUD, neither → no data access.userIdstays the human so owner-stamping andcurrent_user.*RLS resolve to them; the user-derivedsystemPermissionsare cleared so a cap-gated action can't ride the user's capabilities. A token without a client stays ahumanprincipal (not every bearer is an agent).plugin-security— three built-in ceiling sets (mcp_agent_data_read/mcp_agent_data_write/mcp_agent_restricted): pure CRUD bits, no row-level security (all row/owner/tenant narrowing comes from the delegating user on the other side of the intersection). Anagentprincipal skips the additive human baseline (member_default) — its grants are exactly its ceiling — and its fallback is the restricted (no-object-access) set, so a mis-resolved agent fails CLOSED, never open.spec—MCP_AGENT_PERMISSION_SET_*+scopesToAgentPermissionSets(), single-sourced beside the OAuth scope constants;api-surface.jsonregenerated.Behaviour change (a security tightening)
Previously an MCP OAuth request executed with the full authority of the logged-in user, and scopes narrowed only the tool surface (which tools are exposed). Now the scope is also a real data-layer ceiling: a
data:readtoken can never write ANY record — even via a crafted call — no matter what the user could do. This is strictly consistent with the existing contract that "a scope can never grant more than the user could do" (the intersection only ever narrows) and closes the gap where a compromised or confused agent could act with the user's full reach.Tests
@objectstack/runtime(resolve-execution-context):data:read/data:write/actions:execute→ correct ceiling +principalKind:'agent'+onBehalfOf; a token with no client stayshuman. Full runtime suite 491 green (incl. the existing MCP-OAuth dispatcher tests).showcase-agent-scope-ceiling, real SQLite + RLS + private-OWD): adata:readagent acting for a member who owns a record can read it but cannot edit or create; adata:writeagent for the same user can edit; the human control can do both.plugin-security298,plugin-sharing76, and the D10/OWD/permission-zoo dogfood suites unchanged.check:api-surface+tscclean.Scope / follow-ups
data:read/data:writescopes. Per-client custom grants (binding permission sets to a specific OAuth application) remain a follow-up — thesys_oauth_applicationrecord is better-auth-managed and has no grant linkage yet.systemPermissionsare empty, so cap-gated actions are denied to agents by default (fail-safe). Giving agent ceilings specific action capabilities is a follow-up.🤖 Generated with Claude Code