# When must an instance refuse to present a delegated AAIF credential chain **Date of Record:** 2026 November 10 **Scope:** Applies to AAIF (Agent Identity & Authorization Framework) instances that hold a verifiable credential (VC) issued to a *principal agent* and a delegated authorization chain (DAC) of one or more hop credentials, where each hop is signed by the previous holder's key. Does not apply to single-hop bearer tokens, to chains where every hop is re-anchored to the root issuer at verification time, or to purely capability-based systems with no identity binding. ## Core Distinction The distinction that trips most implementations is between **chain validity** and **chain presentability**. A DAC can be cryptographically valid at every hop — every signature verifies, every `scope` is a subset of its parent, every `notAfter` is monotonically decreasing, the root is trusted — and still be *unpresentable* by the current instance. Presentability requires an additional, frequently unchecked invariant: **the instance presenting the chain must be the subject named in the terminal hop, and the terminal hop's subject key must be the key the instance is currently attesting to.** The failure mode is subtle because verification libraries are written to answer "is this chain valid?" and not "is this chain valid *for me to present*?" A verifier that only checks the former will happily accept a chain whose terminal subject is a *different* agent instance — a sibling replica, a previous incarnation of the same logical agent, or an unrelated agent that happened to leak its credential. The chain is valid. The presenter is wrong. The two facts are independent. This is the AAIF-specific form of the confused-deputy problem, but it is sharper than the classical version: in AAIF the terminal hop binds a *public key*, not a bearer secret, so the chain is not stealable in the usual sense — it is merely *presentable by anyone who can produce a signature over the challenge*. That is exactly the set of instances that hold the terminal subject's private key, which in practice is a set larger than one: replicas sharing a keystore, agents restored from a snapshot, agents that rotated keys without re-issuing the terminal hop. ## What Was Observed During a rolling redeploy of a fleet of task-executing agents, instances were restarted with new keypairs while retaining their persisted credential chains on local disk. The terminal hop of each chain still named the *pre-rotation* public key. The attestation service, which validates chains against the root and the embedded public keys, accepted them — every signature over every hop was intact. The instances then presented the chains to downstream resource servers alongside a fresh attestation over a nonce, signed with the *new* key. Downstream verifiers checked the nonce signature against the key in the terminal hop, found a mismatch, and — depending on the verifier's strictness — either rejected the call (correct but noisy) or, in two cases, fell back to validating the chain alone and admitted the call. The second behavior is the dangerous one. It arises because the verifier's contract was "verify the chain," and the chain was valid. The mismatch between the nonce-signing key and the terminal-hop key was treated as a soft signal rather than a hard invariant. The result was a window, lasting the length of the rollout, in which a restarted instance could act under the *full delegated authority* of its predecessor's chain while presenting a key the chain did not bind. No signature was forged. No secret was stolen. The invariant "terminal subject key == attesting key" was simply never asserted by the presenter, because the presenter had no reason to suspect it was its own responsibility. A related observation: sibling replicas sharing a keystore produced *identical* terminal-hop subjects. Two instances could each present the same chain and each sign the nonce correctly, because they shared the key. From the verifier's perspective both were the terminal subject. From an audit perspective, two distinct processes acted under one identity, and the delegation chain's implicit "one holder" assumption was violated without any cryptographic break. ## Condition of Validity The presentability invariant holds — that is, an instance *may* present a chain — if and only if all of the following are true at presentation time: 1. The terminal hop's `credentialSubject.id` equals the instance's own AAIF identifier (not merely its agent *class* or *role*). 2. The terminal hop's `credentialSubject.publicKey` equals the public key the instance is currently using to sign the presentation challenge. 3. The chain's `delegationDepth` at the terminal hop is `0` — no further delegation is outstanding — or, if greater than `0`, the instance intends to delegate and the terminal hop's `allowedDelegatee` constraint admits the intended next subject. 4. The instance's key has not been rotated since the terminal hop was issued, *or* the terminal hop has been re-issued to bind the new key. 5. No sibling instance holds the same terminal-hop key and is concurrently active under the same identity (the single-holder condition). Conditions 1–4 are checkable by the instance itself before it emits the presentation. Condition 5 is not locally checkable and must be enforced by the issuing authority (key uniqueness per active instance) or tolerated explicitly (shared-identity mode, with audit consequences). ## Decision Boundary The boundary is this: **an instance must refuse to present a chain when it cannot prove condition 2 locally.** It must not present a chain whose terminal hop names a key it does not hold, and it must not present a chain alongside a nonce signature made with a key the chain does not bind. The tempting alternative — present the chain and let the verifier sort out the mismatch — pushes a locally decidable invariant onto a remote party that may not enforce it. The two observed admissions above are the direct consequence of that push. The refusal is not a failure of the agent. It is the agent correctly recognizing that its *identity material* and its *authorization material* have diverged, and that continuing would either (a) be rejected downstream, wasting a round trip and polluting audit logs with ambiguous denials, or (b) be admitted downstream under a key the chain does not name, which is an authorization bypass. Refusal is the safe branch; presentation is the unsafe one. The boundary is *not* crossed when the instance holds the terminal-hop key but the chain is stale in scope — that is a scope-subset check, handled separately. Nor is it crossed when the chain's root is untrusted — that is a trust-anchor check. The presentability invariant is orthogonal to both and must be asserted independently. ## Operational Effect Enforcing the invariant locally converts a class of silent authorization bypasses into loud, early, local refusals. The instance detects the divergence at the moment it would have presented, logs the terminal-hop subject and its own current key fingerprint, and either (a) requests re-issuance of the terminal hop from its delegator, or (b) escalates if it cannot reach the delegator. In the rolling-redeploy case above, this would have turned a rollout-length window of ambiguous admissions into a bounded set of re-issuance requests at restart time — a routine, recoverable condition. The cost is that instances must be able to *compare* their current key to the terminal-hop key, which means the key material must be introspectable at presentation time (not sealed behind an HSM boundary that exposes only a signing oracle). Where the key is HSM-resident, the instance must instead compare key *identifiers* — the terminal hop must carry a `keyId` that the HSM can attest to. This is a design constraint on the credential schema, not merely on the runtime. Finally, the invariant makes the single-holder assumption explicit. Where replicas legitimately share a key, the framework should model them as *one* identity with concurrent holders, and the audit trail should record which process presented which challenge — otherwise the chain's "one holder" semantics are quietly false, and every downstream authorization decision inherits that falsity. ```python class ChainNotPresentableError(RuntimeError): """Raised when an AAIF instance holds a valid chain it must not present. The chain verifies against the root and every hop signature is intact, but the terminal hop does not bind the key this instance is currently using. Presenting it would either be rejected downstream or, worse, admitted under a key the chain does not name. """ def assert_chain_presentable( chain: dict, own_agent_id: str, own_public_key_fingerprint: str, ) -> None: terminal = chain["hops"][-1] subject = terminal["credentialSubject"] if subject["id"] != own_agent_id: raise ChainNotPresentableError( f"terminal subject {subject['id']!r} is not this instance " f"({own_agent_id!r}); chain is valid but belongs to another agent" ) if subject["publicKeyFingerprint"] != own_public_key_fingerprint: raise ChainNotPresentableError( "terminal hop binds key " f"{subject['publicKeyFingerprint']!r} but this instance holds " f"{own_public_key_fingerprint!r}; key rotated without re-issuing " "terminal hop -- request re-issuance from delegator" ) if terminal.get("delegationDepth", 0) < 0: raise ChainNotPresentableError("negative delegation depth is malformed") def present(chain, own_agent_id, own_key_fp, nonce_signer): assert_chain_presentable(chain, own_agent_id, own_key_fp) return { "chain": chain, "nonceSignature": nonce_signer.sign(chain["challengeNonce"]), } ```