Skip to content

rfc: oauth scope ceilings - #1224

Draft
ragnorc wants to merge 3 commits into
ModernRelay:mainfrom
ragnorc:rfc/oauth-scope-ceilings
Draft

ragnorc wants to merge 3 commits into
ModernRelay:mainfrom
ragnorc:rfc/oauth-scope-ceilings

Conversation

@ragnorc

@ragnorc ragnorc commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Draft RFC: docs/rfcs/2026-11-01-oauth-scope-ceilings.md, plus its registry row.

What it proposes

An admitted OIDC access token may carry an attenuation-only ceiling, read from its standard scope claim. A request succeeds only when applied policy permits the actor and the ceiling covers the action. The actor stays the person's principal, applied policy stays the only grant, a token without a ceiling behaves exactly as today, and native signed credentials are untouched.

The vocabulary is fixed in the server, six names over the thirteen existing policy actions:

Scope Actions
graph.read read, invoke_query, export
graph.write change, branch_create, branch_delete, branch_merge, commit_publish, commit_drop
graph.schema schema_apply
graph.admin admin
cluster.list graph_list
cluster.config config_manage

A trust document opts in by declaring scope_prefix (frozen with the binding at boot). Without it, scope stays ignored as it is now. With it: prefixed entries must name a vocabulary scope (unknown → 401 invalid_token), unprefixed entries are ignored, and an absent scope is an empty ceiling.

The check sits in the one shared handlers::authorize, after the policy decision, so insufficient_scope (new 403 code, with the RFC 6750 WWW-Authenticate … error="insufficient_scope", scope="…" challenge) is only ever returned for an action the person could perform with a wider scope; forbidden is unchanged and still carries no challenge. Protected-resource metadata gains scopes_supported; /access gains scopes and reports the intersection.

Scope

Scopes only. No provider roles, no act/delegation, no Agent Auth, no signed-credential v3, no client recorded on commits (#1173). Delegated branch-scoped credentials remain #978. Provider-role membership is the separate #1223. Closed #1218 bundled an earlier version of this with those.

Verified against

origin/main 3e0e467: oidc_identity.rs (scope/scp/azp never read; client_id kept beside the actor since #1196), data_tokens.rs (v2 strictly refuses grants/roles), handlers::authorize as the single HTTP/MCP choke point, mcp.rs/challenge.rs (metadata and 401 challenge; no scopes_supported, no insufficient_scope), AccessOutput, ErrorOutput, RFC 0003 and the identity RFC's ceiling paragraph. WorkOS "Access token claims": Connect user tokens carry scope and client_id; DCR/CIMD clients cannot have per-client scopes; Connect tokens have no permissions claim. The entry bounds (256 × 128 bytes, RFC 6749 alphabet) match the control plane's existing M2M validate_scopes.

@ragnorc ragnorc changed the title rfc: OAuth scope ceilings rfc: oauth scope ceilings Nov 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant