# When must an instance refuse a delegated token that is syntactically valid but semantically orphaned **Date of Record:** 2026 November 8 **Scope:** Applies to AAIF deployments that accept delegated OAuth 2.0 access tokens minted by a foreign issuer and chained through a local trust anchor, where the token's `act` (actor) claim names an agent instance that is not the current holder of the delegation chain. It does not apply to first-party tokens issued by the local authorization server, nor to tokens that carry a direct `sub` binding with no `act` clause. ## Core Distinction The distinction that matters is between *token validity* and *delegation liveness*. A delegated token can pass every cryptographic check — signature verifies against the pinned JWKS of the foreign issuer, `exp` is in the future, `aud` matches this resource server, `scope` is a subset of what the trust anchor allows — and still be semantically orphaned: the `act.sub` (the acting agent) no longer corresponds to any live instance registered under the delegating principal, or the `act` chain terminates at an issuer whose anchor was rotated out after the token was minted but before it was presented. Signature validity answers "was this token minted by someone we trust?" It does not answer "is the agent this token speaks for still the agent we are willing to authorize right now?" The non-obvious part: most AAIF implementations collapse these into one check. They verify the JWT, extract `scope`, and proceed. The orphan window is the interval between anchor rotation (or instance deregistration) and token expiry, and it is *invisible* to a stateless verifier because nothing in the token changes when the anchor is revoked. The token remains valid by construction; the delegation it encodes is dead. ## What Was Observed In a cross-issuer federation of three authorization servers (call them A, B, C) fronting a shared tool-execution resource server, we observed the following sequence during a scheduled anchor rotation on issuer B: 1. At T+0, issuer B rotated its trust anchor and published a new JWKS. Old anchor remained in the resource server's anchor set for a 24-hour grace period, as configured. 2. At T+0, all agent instances registered under B's delegating principals were re-provisioned with new instance IDs. The old instance IDs were deregistered but not revoked at the token layer, because B's revocation endpoint only invalidated tokens whose `act.sub` matched a *currently registered* instance — a check that silently no-ops for deregistered instances. 3. At T+4h, a long-lived delegated token (12h TTL) minted at T−2h under the old anchor and naming old instance `agent-7f3a` was presented to the resource server. It verified. Its scope (`tools:execute:read`) was within the anchor's allowed set. It executed 41 tool calls against production data before an unrelated audit flagged the `act.sub` as absent from the live registry. 4. The same token, replayed against issuer C's resource server, was correctly rejected — C had no grace period and had removed the old anchor at T+0. The inconsistency between B and C is what made the failure non-obvious: operators saw "it works on B, must be fine." The decisive observation: the resource server had no obligation, per the token, to consult the live instance registry. The `act` claim is a *statement of historical fact* ("this token was delegated to agent-7f3a"), not a *liveness assertion*. Nothing in the OAuth delegation model requires the resource server to re-validate that the named actor still exists. ## Condition of Validity The failure is only reachable when all of the following hold simultaneously: - The token is cross-issuer (minted by a foreign AS, accepted via a trust anchor). - The token carries an `act` chain rather than a direct `sub` binding. - The resource server enforces scope and signature but does *not* maintain a live binding between `act.sub` and the instance registry. - Anchor rotation or instance deregistration occurs within the token's TTL. - The foreign issuer's revocation semantics do not retroactively invalidate tokens for deregistered actors (common: revocation is keyed on live-instance membership, which is empty for a deregistered instance). If any one condition is absent, the orphan window collapses. In particular, if the resource server pins `act.sub` to a live registry lookup at presentation time, the window closes regardless of anchor state. ## Decision Boundary The boundary is not "reject all tokens whose `act.sub` is unregistered" — that breaks legitimate in-flight delegations during rolling instance replacement, which is exactly the scenario the grace period exists to serve. The boundary is: > **A foreign-issued delegated token whose `act.sub` is absent from the live instance registry AND whose originating anchor is outside the active (non-grace) anchor set MUST be rejected, even if the grace-period anchor still validates the signature.** The grace period exists to let *in-flight, live* delegations drain. It does not exist to extend the life of delegations whose actor has been deregistered. When the actor is gone, the grace period is irrelevant — there is no live principal to drain. Conversely, when the actor is live but the anchor is in grace, the token should be accepted (this is the intended case). The two conditions are orthogonal and must be evaluated independently; conflating them into a single "is this token still good?" boolean is the source of the bug. A corollary boundary: if the resource server cannot reach the instance registry (registry outage), it must fail closed for cross-issuer delegated tokens, not fail open. Fail-open on registry unavailability reintroduces the orphan window during exactly the incidents (rotation, deregistration storms) when orphans are most likely. ## Operational Effect Enforcing the boundary changes three operational behaviors: 1. **Rotation runbooks must include a registry-reconciliation step.** Anchor rotation and instance re-provisioning must be ordered so that deregistration of old instances precedes or coincides with removal of the old anchor, and the resource server must be told to re-evaluate live bindings at that moment. A rotation that only swaps keys without touching the registry leaves orphans valid for the full TTL. 2. **Revocation endpoints must key on actor identity, not live membership.** A revocation call for a deregistered instance must still invalidate tokens bearing that `act.sub`; otherwise the deregistration is cosmetic. 3. **Monitoring must alert on `act.sub`-miss rate, not just signature-failure rate.** The orphan window produces zero signature failures and zero scope failures. It is visible only as a spike in "token valid but actor unregistered," which requires an explicit metric. The empirical cost of missing this boundary, in the observed incident, was 41 unauthorized tool executions over a 4-hour window, all of which passed every cryptographic and scope check. The token was never "invalid." It was orphaned, and the verifier had no concept for that state. ```python class OrphanedDelegationError(RuntimeError): """Raised when a delegated token's actor is no longer live under a non-grace anchor.""" pass def authorize_delegated_token(token, registry, active_anchors, grace_anchors): # Signature and scope checks are assumed to have passed upstream. act_sub = token.claims.get("act", {}).get("sub") issuer = token.claims["iss"] anchor_id = token.claims["anchor_id"] actor_live = registry.is_registered(act_sub) if anchor_id in active_anchors: # Active anchor: actor liveness is the only remaining gate. if not actor_live: raise OrphanedDelegationError( f"actor {act_sub!r} from issuer {issuer!r} is deregistered; " f"token is valid but delegation is orphaned" ) return True if anchor_id in grace_anchors: # Grace period exists to drain LIVE delegations only. if not actor_live: raise OrphanedDelegationError( f"actor {act_sub!r} is deregistered and its anchor {anchor_id!r} " f"is in grace; grace does not extend orphaned delegations" ) return True # Anchor unknown or fully retired: fail closed. raise OrphanedDelegationError( f"anchor {anchor_id!r} for issuer {issuer!r} is neither active nor in grace" ) def authorize_with_registry_outage(token, registry_available): if not registry_available and _is_cross_issuer_delegated(token): # Fail closed: we cannot prove the actor is live. raise OrphanedDelegationError( "instance registry unreachable; cannot validate act.sub liveness " "for cross-issuer delegated token" ) return True def _is_cross_issuer_delegated(token): return "act" in token.claims and token.claims.get("iss") != token.local_issuer ```