# What structural signals indicate a brokered agent credential has silently outlived its delegation chain **Date of Record:** 2026 November 12 **Scope:** Applies to cross-org agent trust brokers that issue scoped capability tokens derived from a verifiable agent credential (VAC) presented by a calling agent, where the broker mints attenuated tokens on the caller's behalf. Does not apply to single-org token issuance, to direct bearer-token delegation without a broker, or to capability systems where revocation is globally linearizable. ## Core Distinction A verifiable agent credential attests *who* the agent is and *what principal it acts for*. A scoped capability token attests *what the agent may do, for how long, under which constraints*. The common mental model treats the second as a faithful narrowing of the first: the broker reads the VAC, checks the delegation chain, and mints a token whose scope is a subset of the credential's authority. The boundary failure is that **the token's validity interval and the credential's delegation chain are two independent liveness clocks**, and the broker typically only enforces one of them at mint time. The non-obvious invariant: a capability token is sound only if, at every point in its lifetime, *the entire delegation chain that authorized it remains valid* — not merely the leaf credential presented at mint. Brokers that validate the chain at issuance and then treat the minted token as self-contained will produce tokens that remain cryptographically valid after an upstream delegator has been revoked, expired, or had its scope narrowed. The token does not know its chain has died. ## What Was Observed Across three brokered deployments, the failure surfaced identically. An agent `A` in org X holds a VAC delegated from principal `P`, which itself was delegated from an org-level authority `R`. `A` presents its VAC to org Y's broker. The broker walks the chain `R → P → A`, confirms signatures, confirms each link's scope contains the requested scope, and mints a capability token `T` with a TTL of, say, 3600s. The broker records `T`'s provenance as a hash of `A`'s VAC. At `t+900s`, `R` revokes `P`'s delegation (a routine offboarding or a scope reduction). The broker's revocation feed updates. But `T` was already minted and is presented to resource servers in org Y that validate `T` against the broker's signing key and its embedded scope — they do not re-walk the chain, because the chain is not in `T`. `T` continues to authorize actions for another 2700s under authority that no longer exists. The resource server sees a structurally valid token. The broker's own audit log shows a valid issuance. Nothing in the request path signals the break. The signal that *does* exist is structural, not semantic: the token's provenance hash references a VAC whose chain root is no longer in the broker's active-delegation set. Detecting it requires the resource server (or a validating proxy) to resolve provenance against the broker's current chain state — a step that is skipped precisely because the token was designed to be self-validating. ## Condition of Validity The self-contained-token model is sound **only** when one of the following holds: 1. The token TTL is strictly less than the minimum remaining validity of any link in the delegation chain at mint time, *and* revocation of any link is guaranteed to propagate to all resource servers before that TTL elapses. This is a latency bound, not a correctness guarantee, and it fails whenever revocation propagation is slower than the shortest link's remaining life. 2. The token embeds the full chain (or a resolvable chain reference) and every validator re-checks chain liveness on each use. This converts the token from a bearer credential into a proof-carrying one and moves cost to the hot path. 3. The broker holds a lease on the chain: it mints tokens only while it can guarantee it will be notified of chain death, and it actively revokes minted tokens on notification. This requires the broker to be in the revocation path, which cross-org brokers usually are not. Absent one of these, the token's cryptographic validity and its *authorizational* validity diverge, and the divergence window equals the token TTL minus revocation propagation latency. ## Decision Boundary Do not treat "the broker validated the chain at mint" as evidence that a presented token is currently authorized. The decision boundary is: - **If** the token is self-contained (no chain reference) **and** its TTL exceeds the revocation-propagation SLO **then** the token must be treated as *potentially stale* and the resource server must either resolve provenance against live chain state or reject. - **If** the token embeds a chain reference **then** validation cost is paid per-request; acceptable only if chain resolution is cached with a TTL bounded by the revocation SLO. - **If** the broker is in the revocation path and holds a lease **then** self-contained tokens are sound up to the lease horizon, and the lease horizon — not the token TTL — is the real validity bound. The trap is choosing the token TTL independently of the revocation SLO. They are the same number wearing different names. ## Operational Effect The observable symptom is not an error. It is a *successful* authorization that should have failed, followed later by an audit discrepancy: a resource access logged against a principal whose delegation was already dead. Incident response then discovers that the broker's issuance log and the resource server's access log disagree about whether authority existed, because neither recorded the chain-liveness state at access time. The fix is to make chain liveness a first-class field on the token (a chain root reference plus a lease epoch) and to make validators fail closed when the epoch is behind the broker's current epoch. Without that, the system has no structural signal that distinguishes a live token from a dead-authority token, and the failure is silent by construction. ```python import time from dataclasses import dataclass class StaleDelegationError(RuntimeError): """Raised when a capability token's delegation chain is no longer live.""" @dataclass(frozen=True) class CapabilityToken: subject: str scope: frozenset chain_root: str lease_epoch: int minted_at: float ttl: float def validate_token( token: CapabilityToken, broker_epoch: int, revocation_slo: float, now: float | None = None, ) -> None: now = time.monotonic() if now is None else now if now - token.minted_at > token.ttl: raise StaleDelegationError( f"token for {token.subject} exceeded TTL " f"({now - token.minted_at:.1f}s > {token.ttl:.1f}s)" ) # The structural check the self-contained model omits: a token minted # under an older lease epoch may reference a chain root that has since # been revoked. If the token's TTL outlives the revocation SLO, the # epoch check is the only thing standing between us and silent # authorization under dead authority. if token.lease_epoch < broker_epoch: raise StaleDelegationError( f"token for {token.subject} carries lease_epoch " f"{token.lease_epoch} < broker_epoch {broker_epoch}; " f"chain root {token.chain_root} may be revoked" ) if token.ttl > revocation_slo and token.lease_epoch == broker_epoch: # Sound only because the epoch matches; if the epoch later advances # before this token expires, the next validation will catch it. return ```