# Why does an instance identity rotation silently invalidate in-flight delegated tokens in zero-trust multi-agent meshes **Date of Record:** 2026 November 10 **Scope:** Applies to multi-agent meshes where each agent instance mints short-lived bearer tokens (JWTs or macaroons) bound to a rotating instance identity, and where those tokens are delegated downstream across at least one hop. Does not apply to long-lived service-account credentials, single-hop request/response calls with no delegation, or meshes that re-issue tokens on every hop rather than propagating them. ## Core Distinction The failure is not "token expiry." It is the distinction between **token validity** (the token's own `exp`/`nbf` and signature) and **binding validity** (whether the token's subject identity still corresponds to a live, authoritative instance identity at the verifier). A token can be cryptographically valid, unexpired, and correctly signed, yet be rejected — or worse, accepted against a stale identity — because the instance identity it was minted under has rotated since minting. The non-obvious part: rotation is *supposed* to be transparent to in-flight work. The mesh's rotation controller revokes the old identity's signing key and publishes the new key in the trust bundle. But delegated tokens carry the *old* identity as their `sub`/`iss` claim. Downstream verifiers that resolve `sub` against the live identity directory (rather than against the token's embedded key lineage) will either (a) fail to resolve, treating the token as forged, or (b) resolve to the *new* identity and conflate two distinct principals. Neither is the intended semantics. ## What Was Observed During a rolling rotation window (default 90s overlap), a supervisor agent minted a delegation token `T` for a worker agent at `t0`. The token carried `iss=instance-A`, `exp=t0+300s`. At `t0+40s`, instance A rotated: signing key `K_A` was retired, `K_A'` published, and the identity directory marked `instance-A` as superseded by `instance-A'`. At `t0+120s` the worker presented `T` to a tool server. The tool server: 1. Verified the signature against the trust bundle — **passed**, because `K_A` was still in the overlap window. 2. Resolved `iss=instance-A` against the live directory — **failed**, because the directory no longer listed `instance-A` as active. 3. Returned `401 invalid_subject`, and the worker retried against the supervisor, which had *also* rotated and could no longer re-mint under `instance-A`. The result was a delegation deadlock: a token that was valid for another 180 seconds was unusable, and the only party who could re-issue it had shed the identity required to do so. In a second observed variant, the directory retained `instance-A` as an alias of `instance-A'`; the tool server then accepted `T` but attributed the call to `instance-A'`, silently widening the worker's effective privilege set because `instance-A'` had a broader policy attachment. ## Condition of Validity The invariant that must hold for safe delegation across rotation is: > For every delegated token `T` with subject `s`, the verifier must be able to resolve `s` to the *exact* identity (key lineage, policy attachment, and revocation epoch) that was authoritative at `T`'s minting time, for the entire lifetime of `T` — or `T` must be rejected before use. This holds only when the trust bundle carries a **historical** record of retired identities keyed by the token's `kid`/`iat`, not merely the current active set. It fails when the directory is a *current-state* view (active identities only) rather than an *append-only identity ledger*. It also fails when rotation overlap windows are shorter than the maximum delegation token lifetime — a common misconfiguration, since operators size the overlap for direct calls (seconds) and forget delegated tokens live for minutes. ## Decision Boundary Choose the strategy by token lifetime versus rotation overlap: - If `max_token_lifetime <= rotation_overlap`, current-state directory resolution is safe; the token cannot outlive its identity. - If `max_token_lifetime > rotation_overlap` (the common case), you must either (a) maintain an append-only identity ledger and resolve `sub` at `iat`, or (b) force re-minting on rotation by having the supervisor track outstanding delegations and revoke them at rotation, accepting the deadlock risk if the supervisor itself rotates mid-flight. - Never resolve a delegated token's `sub` against the *current* active set when an alias/supersession mapping exists; that path silently re-principals the call. The boundary is crossed the moment a token's lifetime exceeds the overlap window *and* the verifier uses current-state resolution. That combination is silent: it passes signature checks and only manifests as either spurious 401s or privilege widening. ## Operational Effect In production this presents as intermittent, rotation-correlated authorization failures that do not reproduce under load tests (which rarely span a rotation), and as rare privilege-widening incidents that are nearly invisible in logs because the call *succeeds*. Mitigation: emit the resolved identity lineage alongside every authorization decision, alert when `token.iat < identity.superseded_at`, and size rotation overlap to `max_token_lifetime + clock_skew`. Treat any authorization decision where the token's minting identity is not the resolved identity as a hard error, not a warning. ```python class IdentityRotationBindingError(RuntimeError): """Raised when a delegated token's minting identity no longer matches the identity resolved by the verifier, indicating rotation drift.""" pass def authorize_delegated_token(token, identity_ledger, now): # token fields: sub, iat, exp, kid if now >= token.exp: raise IdentityRotationBindingError( f"token expired: exp={token.exp} now={now}" ) minting_identity = identity_ledger.resolve_at(token.sub, token.iat) if minting_identity is None: raise IdentityRotationBindingError( f"no identity record for sub={token.sub} at iat={token.iat}; " "ledger is current-state only or rotation overlap too short" ) current_identity = identity_ledger.resolve_current(token.sub) if current_identity is None: # Identity rotated out entirely; token is orphaned. raise IdentityRotationBindingError( f"sub={token.sub} has no current identity; token orphaned by rotation" ) if current_identity.lineage_id != minting_identity.lineage_id: # Supersession alias present: would silently re-principal the call. raise IdentityRotationBindingError( f"sub={token.sub} minted under lineage " f"{minting_identity.lineage_id} but resolves to " f"{current_identity.lineage_id}; refusing to re-principal" ) if token.kid not in minting_identity.active_keys_at(token.iat): raise IdentityRotationBindingError( f"token kid={token.kid} not valid for sub={token.sub} at iat={token.iat}" ) return minting_identity.policy_attachment ```