# Why does an instance identity claim survive token expiry in AAIF scoped delegation chains **Date of Record:** 2026 November 12 **Scope:** Applies to AAIF attestation chains where a delegated agent presents a scoped delegation token whose issuer chain terminates at a root instance identity with a longer TTL than the leaf token. Does not apply to single-hop tokens with no delegation, nor to tokens whose `act` (actor) chain is cryptographically unbound to the presented instance key. ## Core Distinction There are two distinct clocks in an AAIF delegation chain: the **token validity clock** (the `exp`/`nbf` of the scoped delegation token presented at the resource) and the **attestation validity clock** (the TTL of the underlying instance identity attestation, plus the revocation status of each intermediate delegator). Verifiers routinely conflate them. A token can be unexpired while the identity that authorizes it is no longer attestable — or, the failure mode documented here, a token can be expired while the *instance identity claim* embedded in its `sub`/`iss` is still treated as live by downstream caches. The non-obvious invariant: **a scoped delegation token's expiry does not invalidate the instance identity assertion it carries.** The identity assertion is a property of the workload (bound to the instance key and its attestation quote), not of any single token. Revoking or expiring the token removes the *scope grant*, not the *identity*. Systems that treat token expiry as identity expiry, or (worse) treat identity liveness as token liveness, produce a boundary where a re-issued token can be minted from a stale identity cache without re-attestation. ## What Was Observed In a three-hop chain — root instance `I0` → delegator agent `A1` → scoped worker `A2` — the leaf token `T2` carried `exp = T0+300s` and an `act` chain recording `A1` acting for `I0`. The instance attestation for `I0` had a 24h TTL. At `T0+301s`, `T2` correctly failed signature-independent expiry checks. However, the AAIF credential service, on receiving a re-mint request from `A2` presenting the *original attestation quote* of `I0` (cached at `T0`), issued `T2'` with a fresh `exp` **without re-validating that `A1` had not been revoked at `T0+120s`**. The re-mint path checked `I0`'s attestation TTL (valid) and `A2`'s key binding (valid) but treated the intermediate `act` entry as a static historical record rather than a live delegation that must remain unrevoked at mint time. The result: a 180-second window in which a revoked intermediate delegator continued to authorize fresh leaf tokens, because the leaf token's expiry (the thing everyone watched) was orthogonal to the intermediate revocation (the thing that actually mattered). ## Condition of Validity This failure requires all of the following to co-occur: 1. **Multi-hop delegation** where at least one intermediate (`A1`) is a *delegator* whose revocation is checked only at the leaf token's original issuance, not at re-mint. 2. **A re-mint or refresh path** that accepts the root attestation quote as sufficient proof of the full chain, rather than re-walking the `act` chain against current revocation state. 3. **Disjoint TTLs**: root attestation TTL ≫ leaf token TTL, so the leaf expires many times within a single attestation lifetime, creating repeated re-mint opportunities. 4. **A revocation propagation delay** at the intermediate that exceeds zero at the moment of re-mint. Absent any one condition, the chain fails closed (rejected) or the window collapses to zero. Single-hop tokens are immune; chains with equal TTLs expose only a single re-mint at the boundary. ## Decision Boundary The correct rule is: **re-mint must re-validate the entire `act` chain's revocation status at mint time, not merely the root attestation TTL.** The decision boundary is not "is the identity still attested?" but "is every delegation edge still live *now*?" Concretely: - If `now < T2.exp`: present token is valid; verify normally. - If `now >= T2.exp` and a re-mint is requested: walk `act` from leaf to root; for each intermediate, query current revocation state. If any edge is revoked, **refuse re-mint and surface the revoked edge** — do not silently fall back to the root attestation. - The root attestation TTL gates only whether `I0` may be *referenced*, never whether a *new grant* may be issued through a revoked intermediate. A verifier that cannot distinguish "identity expired" from "scope expired" from "intermediate revoked" will collapse these into a single boolean and lose the ability to make this decision. ## Operational Effect The practical consequence is a **revocation-bypass window** bounded by the leaf token TTL, not by any revocation SLA. Operators who instrument only token expiry will see green dashboards while a revoked delegator continues to mint. The fix is to make the re-mint path's revocation walk a hard precondition and to emit a distinct error class when an intermediate edge is dead but the root is live — this is the signal that a downstream cache is holding a stale `act` chain. Treat the `act` chain as a live dependency graph, not a certificate of provenance, and the boundary disappears. ```python class DelegationChainError(RuntimeError): """Raised when an AAIF delegation chain cannot be validly re-minted.""" pass class RevokedIntermediateError(DelegationChainError): """Intermediate delegator is revoked, but root attestation is still live.""" def remint_scoped_token(act_chain, root_attestation, now, revocation_lookup): """ act_chain: list of actor entries from leaf to root, each with 'id' and 'exp'. root_attestation: dict with 'subject', 'exp'. revocation_lookup: callable(actor_id) -> bool (True if revoked). """ if now >= root_attestation["exp"]: raise DelegationChainError( f"root attestation for {root_attestation['subject']} expired at " f"{root_attestation['exp']}; cannot re-mint" ) # The non-obvious invariant: every intermediate edge must be live NOW. for actor in act_chain: if revocation_lookup(actor["id"]): raise RevokedIntermediateError( f"intermediate delegator {actor['id']} revoked at re-mint time " f"(now={now}); root attestation still live, refusing re-mint " f"to avoid revocation-bypass window" ) leaf = act_chain[0] return { "sub": leaf["id"], "act": [a["id"] for a in act_chain], "exp": min(now + 300, root_attestation["exp"]), } ```