When the Credential Gets Wider Than the Check
The incident that motivates this paper has a simple structure. A vendor ships a more capable model into the same integration tier — same API surface, same credential, same permission set — without re-vetting the expanded capability against the access it inherits. The access boundary was drawn for the prior model. The new model is stronger; the boundary is unchanged; the gap is silent. No alert fires. No approval gate is triggered. The integration keeps running, and the new model exercises permissions that were never evaluated against what it can now do with them.
This is the access-engineering version of the silent scope expansion risk. The platform's own codebase names it explicitly in a comment anchored to ARC-568: if workspace membership were request-resolved rather than config-resolved, a credential scoped to workspace A could not be silently reused in workspace B — but today the workspace boundary is enforced at config time, and the membership check is the load-bearing gate until request-resolution lands. 10 The problem is structural: the authorization boundary is a function of when the boundary was drawn, not of what the model inside it can now do.
SR 26-2 (the 2026 successor to SR 11-7) treats a model upgrade as a change event that triggers the validate pillar: the revised guidance requires ongoing monitoring on a risk-based cadence, including detection of drift, and makes the effective-challenge obligation continuous rather than point-in-time. 11 The access-engineering consequence is direct. A capability tier is not a static credential; it is a promise about the maximum scope of action a model in that tier can take. When the model changes, the promise must be re-evaluated — not because the credential string changed, but because the set of actions the model can take with that credential may have expanded. A tier escalation is change management. A silent model upgrade inside a fixed tier is a change management gap.
A model upgrade is not a no-op for the authorization boundary. The credential inherited by the new model was drawn for the prior one. The gap between what the boundary permits and what the new model can do with that permission is the attack surface that access engineering must close.
Three Enforcement Layers: Identity, Permission, Resource
Authorization in an agent-native system is not a single check. It decomposes into three layers that fail independently — which means each must be engineered independently, and a gap in any one of them is not compensated by the other two.
The first layer is identity. Before any permission is evaluated, the session must be authenticated: the bearer token extracted from the request, resolved against the database, and validated for freshness. The platform's session-verification pipeline handles this in a single dependency injection: it extracts the token, performs the database lookup, transparently refreshes on a near-expiry token, and returns a fully-hydrated session object — or raises HTTP 401. 1 The refresh is invisible to the caller. The caller never holds a partially-verified session. The layer either resolves completely or it rejects; there is no fallback path that allows a partially-authenticated caller through.
The second layer is permission. Given a resolved session, the platform checks whether the caller holds every permission the endpoint requires. The require_permission dependency iterates the permission set, collects every missing entry, and raises HTTP 403 with the complete deficit list — not just the first missing permission. 2 This is all-of semantics with no OR gate: a caller who holds eleven of the twelve required permissions is refused with the same finality as a caller who holds none. The 403 body names the missing permissions, which makes the refusal auditable and makes the authorization policy debuggable without requiring a read of source code.
The third layer is resource. Given a resolved session and a verified permission set, the caller still may not access every resource the permission nominally covers. Workspace membership is the primary resource-level gate: a session that holds projects:view can view projects only in workspaces of which the caller is a member, enforced by the require_workspace_membership dependency. 7 A permission without a resource gate is a flat credential; the resource layer is what turns a permission into a scoped one.
Each layer is a distinct failure mode. An identity failure (expired token accepted, refresh not performed) admits an unauthenticated caller. A permission failure (OR gate instead of AND gate, missing permission in the check) widens the access surface beyond what the policy intended. A resource failure (membership check skipped, workspace boundary config-resolved instead of request-resolved) allows cross-workspace leakage within a valid permission. NIST AI RMF MANAGE 4.1 frames this decomposition as risk treatment: each failure mode has its own treatment, and the treatment must be addressed, not assumed-away by proximity to a neighboring layer. 13
The Permission Lattice
The canonical permission set is a 16-element union type defined in a single source location. 3 Sourcing the authoritative list in one place is itself an engineering decision: a permission that exists in two places can drift, and a permission that drifts is an untested assumption about what the authorization policy actually covers. The full set, in its natural groupings, is:
users:view·users:manageprojects:view·projects:create·projects:approve·projects:cancel·projects:delete·projects:exportagents:view·agents:manageintegrations:view·integrations:managebilling:view·billing:manageanalytics:viewsettings:manage
The grouping is not cosmetic. Each namespace covers a distinct domain of state: user identity and role assignment, project lifecycle, agent configuration, third-party integrations, financial operations, analytics access, and platform configuration. Within each namespace, the split between :view and :manage (or the more granular lifecycle verbs for projects) enforces the principle of least privilege at the interface level: a caller who needs to read integration state does not inherit the ability to mutate it.
Roles are convenience groupings, not the authorization primitive. The platform defines three roles — admin, developer, and readonly — as named collections of permissions, 4 but the authorization check operates on the resolved permission set, not the role string. This is the distinction between role-based and permission-based enforcement: a role is a shorthand for a permission assignment; the permission is what the enforcement layer tests. An admin who has been stripped of billing:manage cannot manage billing regardless of what their role label says, because the check reads the permission set, not the label.
The all-of semantics have a corollary that is worth naming: there is no permission escalation path inside the lattice. A caller cannot combine two lesser permissions to synthesize a greater one; a caller cannot use users:view and projects:view together to unlock projects:approve. Each permission is atomic; each check is an exact-match membership test. ISO/IEC 42001:2023 Clause 6.1.2 frames this as AI risk assessment: the controls that bound the risk must be evaluated independently, not assumed to compose. 14 An OR gate between permissions is a control that fails to be independent; the all-of check is the architecturally correct form.
Workspace Scoping as Isolation Boundary
A permission that is not scoped to a resource boundary is not least-privilege; it is flat access wearing the costume of policy. The workspace is the primary resource boundary in the platform: a project belongs to a workspace, an agent runs inside a workspace, and an integration is registered against a workspace. The require_workspace_membership dependency enforces that a caller with a valid permission set can act only on resources in workspaces of which they are a member. 7
The implementation carries an explicit scaffolding comment that is worth reading as a governance artifact rather than a code annotation. The comment, anchored to ARC-568, states that workspace membership is currently config-resolved — the workspace is determined at platform configuration time — and that the check will become load-bearing when workspaces are request-resolved, meaning the workspace is determined per-request from the caller's context. 10 The distinction matters for isolation. In a config-resolved model, a credential scoped to workspace A can be silently promoted to workspace B if the configuration changes without re-vetting; the credential follows the config, not the original authorization intent. In a request-resolved model, the workspace is an intrinsic property of the request, and a credential scoped to workspace A cannot access workspace B regardless of what the configuration says, because the workspace is resolved from the request itself.
The membership check is present and enforced today. Its isolation guarantee — the guarantee that a credential cannot silently cross workspace boundaries — is currently as strong as the config path, not as strong as request-resolution would make it. This is documented in Section 9 of this paper as a named limit, not suppressed as an implementation detail. The DO-178C principle that a safety function must be traceable from requirement to evidence applies here: the workspace isolation requirement is stated; the evidence that it holds is the config-resolution path; the gap between that evidence and a request-resolved guarantee is the residual that must be named, not assumed away. 12
The membership check is what prevents a valid credential from being silently promoted across workspace boundaries. Its isolation guarantee is exactly as strong as the path that resolves the workspace. When that path is config-resolved, the guarantee is config-time strong. Request-resolution is the next engineering step; the gap between here and there is the documented residual.
Action Tracing: Every Consequential Decision
Authorization without a trace is a policy without a record. A caller who holds the right permission, passes the workspace check, and takes a consequential action has — from the authorization layer's perspective — done exactly what the policy allows. The question the trace answers is not whether the action was permitted but whether it was taken: who, when, what state was it operating on, what did it decide, and what transition resulted. The distinction between description and evidence is the spine of the KellerAI governance approach: a description of what a system does is not the same thing as a record of what it did, and the record must be durable enough to survive independent challenge.
The platform surfaces the decision trace through a dedicated retrieval endpoint: GET /projects/{id}/decision-trace, gated by session verification and the projects:view permission. 9 The access-gating on the trace itself is a design choice worth naming explicitly. An unrestricted trace endpoint is a reconnaissance surface: an attacker who cannot act on a resource may still be able to read its decision history and use that reading to plan a future action. By placing the trace behind the same permission gate as the resource it documents, the platform ensures that the act of reading the audit trail is itself an authorized act — the trace is not a public window into private state.
The trace record carries machine-readable structure rather than free-text narrative. Each entry records the actor (resolved session identity), the timestamp, the action taken, the prior and resulting state, and — for agent-originated decisions — the observation citations that grounded the decision. This is the difference between a decision log and a decision trace: a log records that something happened; a trace records what evidence the decision was made against. The SR 26-2 effective-challenge requirement is, at bottom, a demand for exactly this — not evidence that a decision was taken, but evidence that the decision was taken against a reconstructable, independently verifiable basis. 11 A status-history record that carries only state transitions satisfies the log requirement; it does not satisfy the effective-challenge requirement.
SSE (Server-Sent Events) streams carry real-time decision events to connected clients during active agent runs. Each stream event is attributed to its vendor (where a third-party model is involved) and carries the action type and the agent session that produced it. The stream is ephemeral; the trace record is durable. Together they provide two temporal windows on the same consequential decision: the real-time window for active monitoring, and the durable record for retrospective audit. A governance program that relies on only one of the two has either a monitoring gap or a retrieval gap; a complete trace architecture must have both.
Vendor Credential Boundaries
A system that manages its own authorization carefully but hands wide credentials to vendor integrations has not contained its attack surface — it has moved the perimeter outward to a boundary it does not control. Vendor credential engineering is therefore not a secondary concern; it is the continuation of the same authorization discipline applied to the integration tier.
The GitHub integration illustrates the pattern. The OAuth authorization flow begins by constructing a signed state token: HMAC over a payload that carries the workspace_id and user_id of the initiating session, with a 10-minute TTL enforced at callback. 8 The state token is not a session credential; it is a single-use nonce whose purpose is to bind the OAuth callback to the workspace and user that initiated the flow and to prevent cross-site request forgery at the OAuth layer. When the callback arrives, the platform verifies the HMAC signature against the platform secret before accepting any part of the payload — a vendor-originated callback that cannot prove it was signed by the platform is rejected before any state is read.
Scope selection is where the credential width decision lives. A GitHub OAuth token scoped to all repositories in an organization is a wide credential; a token scoped to selected repositories is a narrow one. The authorization difference is not the token format — both are bearer tokens with the same structure — but the set of resources the token covers. An integration that requests the wider scope because it is the default, or because it is easier to implement, has made a least-privilege tradeoff silently. The platform's integration code targets the narrower scope; the policy choice is explicit in the authorization request, not inherited from a default.
Token material is never persisted. The OAuth access token is used in-process during the integration session and discarded; only the authorization metadata (workspace, user, integration state) is written to the database. 8 This is the token-lifecycle analog of the permission-layer's all-of check: the least-privilege principle applied to storage rather than to access. A token that is not persisted cannot be exfiltrated from the database, cannot be replayed from a backup, and cannot accumulate in a credential store that becomes its own attack surface. The webhook endpoint enforces the same discipline from the inbound direction: every incoming event payload is HMAC-verified against the platform secret before any processing begins. An event that does not carry a valid signature is dropped before it can influence any platform state.
Capability Tier Registry & Approval Workflow
A permission lattice governs what a caller can do at a given moment. A capability tier registry governs which models are authorized to operate at which permission level — and what process must be completed before a new model enters a tier or a model already in a tier acquires expanded capabilities. The two structures are complementary: the lattice enforces at runtime; the tier registry enforces at change management time. Together they close the silent scope expansion gap that opens when a vendor ships a stronger model into a fixed tier.
SR 26-2 maps directly onto this structure through its effective-challenge mapping. The revised guidance requires that model changes — including changes to vendor-supplied models — pass through the validation pillar before deployment: conceptual soundness review, outcomes analysis, and ongoing monitoring on a risk-based cadence. 11 An effective-challenge mapping for a capability tier translates this into concrete access-engineering terms: for each tier, enumerate the permissions the tier covers, the maximum actions a model in that tier can take with those permissions, the validation evidence required before a model enters or re-enters the tier, and the change-management event that records the authorization.
The project lifecycle state machine is the running implementation of this pattern for platform-internal capability escalation. Each state transition — submit, approve, cancel, delete — is gated by its own permission, 6 which means the state machine cannot advance without an actor who holds the transition-specific credential. An agent that holds projects:create can submit; it cannot approve its own submission, because approval requires projects:approve, which a submitting agent does not hold. The no-self-approval constraint is structurally enforced by the permission separation, not by a runtime check against the submitter's identity.
The administrative surface follows the same pattern at the user-management level. Endpoints that assign roles, send invitations, and deactivate accounts are all gated by users:manage; 5 a developer or readonly session that reaches those endpoints receives HTTP 403 with the missing permission named. The gate is not a soft UI control — it is enforced at the API layer, independent of whatever the frontend presents. A caller who bypasses the UI and reaches the API endpoint directly encounters the same permission check. The defense-in-depth principle that DO-178C formalizes for avionics — no single thread of authority for a safety function — manifests here as enforcement at the transport layer, not at the presentation layer. 12
Least-Privilege Pathways
Least privilege is not a permission name; it is a design discipline that shows up in how permissions are separated, how state transitions are enforced, and how the authorization layer is integrated with the audit trail. Three pathways in the implementation make the discipline concrete.
Symmetric permissions. Each namespace that carries mutable state has a corresponding read-only permission. integrations:view grants read access to integration state without granting the ability to mutate it. billing:view grants read access to financial state without granting the ability to initiate transactions. 3 The symmetry is not accidental; it is the permission-layer expression of the principle that a caller who needs to observe state should not be required to hold a credential that could modify it. A monitoring agent that reads integration health should hold integrations:view, not integrations:manage. The distinction is the difference between a read path and a write path, enforced at the credential level rather than at the application level.
Separate concerns across state-machine transitions. The project lifecycle enforces four separate permissions across its four transition verbs. 6 This separation has a structural consequence: no single permission covers the full lifecycle. A caller cannot submit and approve the same project without holding both projects:create and projects:approve, and the role design ensures that no role assigned to a submitting agent carries both. The no-self-approval constraint emerges from this design as a structural property rather than a runtime rule. Runtime rules can be bypassed; structural properties cannot, because the bypass would require a permission the bypassing caller does not hold.
State-machine-enforced transitions. The combination of permission gating and state-machine enforcement means that the authorization policy is not expressed in conditional logic scattered across the codebase — it is expressed in the permission registry and the state type, and enforced at every transition point by the same dependency. A new transition that is added to the lifecycle but not registered in the permission lattice is a gap that is visible at the type level; the authorization check will fail to compile or will fail at runtime with a missing-permission error, rather than silently permitting the action. The type-level enforcement is the access-engineering analog of the test-coverage requirement in DO-178C: every requirement must be traceable to a test, and every permission must be traceable to an enforcement point. 12
Honest Limits
A governance paper that does not name what is not shipped is not a governance paper. The following limits are stated precisely because a claim that papers over its residual is, in the authorization domain, the exact failure mode the paper argues against. Each limit below is a gap in the current implementation, not a limitation of the architectural pattern.
Workspace is config-resolved today. The isolation guarantee provided by require_workspace_membership is as strong as the config path that resolves the workspace. A credential that was authorized for workspace A at config time can, if the config is mutated, be promoted to workspace B without re-authorization. Request-resolution — where the workspace is an intrinsic property of the request and cannot be redirected by config mutation — is the next engineering step and is explicitly scoped in ARC-568. 710 Until that lands, the workspace boundary is a config-time guarantee, not a request-time one.
Auto-enrolment is blanket. When a user is added to the platform, role assignment determines their initial permission set. The assignment is role-based rather than capability-assessed: a developer role carries the developer permission set uniformly, regardless of the specific resources the new user actually needs to access. Scoped onboarding — where a new user's permission set is derived from the specific workspaces and projects they are authorized to work on — is not yet implemented. The current design trades precision for operational simplicity; the residual is that a new user may hold broader permissions than their initial workload requires.
No escape-rate measurement. The authorization layer knows when a permission check passes and when it fails — the 403 is logged — but the platform does not currently aggregate permission-denial rates by caller, by permission, or by time window to detect anomalous access patterns. An agent that systematically probes permission boundaries will produce a series of 403 responses that are individually logged but not collectively analyzed. Anomaly detection over the authorization event stream is the monitoring complement to the permission lattice, and it is not yet present. NIST AI RMF MANAGE 4.1 frames this as a risk treatment gap: the risk is identified (anomalous permission probing); the treatment (aggregated anomaly detection) is not yet implemented. 13
No backtest harness for permission changes. When the permission lattice is extended — a new permission added, a role's permission set modified — there is no automated harness that replays historical authorization decisions against the new lattice to verify that no existing workflow is inadvertently broken or widened. SR 26-2's outcomes-analysis requirement for model changes applies by analogy: a lattice change is a policy change, and a policy change without a replay harness cannot evidence that its effects are bounded to the intended scope. 11 The gap is named here rather than treated as an accepted residual, because accepting it without naming it would be the access-engineering version of the confident summary that papers the gap over.
The brief companion to this paper — Access Engineering — introduces the core argument in a shorter form. Related papers: When Access Is the Safeguard examines tiered access governance in the first two-tier frontier model release; and The Supervisor's Mirror maps the SR 26-2 model-inventory schema to the evidence discipline that makes an authorization policy defensible under independent challenge.
End of paper
↑ Back to top