# What happens when an instance drops AAIF attestation headers during chained agent-to-agent delegation **Date of Record:** 2026 November 14 **Scope:** Applies to multi-hop agent delegation chains where each hop forwards an AAIF (Agent Authority & Identity Framework) attestation token alongside the request, and where downstream services perform attestation-based authorization. Does not apply to single-hop calls, to chains that re-mint attestations at every hop (full re-attestation model), or to systems where the authorization decision is made solely on the first hop and trust is transitively assumed. ## Core Distinction There are two structurally different ways an attestation can be "lost" in a delegation chain, and they have opposite operational consequences: 1. **Attestation *rejection*** — a hop receives an attestation it cannot validate (expired, wrong audience, bad signature) and either fails closed or strips it. This is loud. It produces an error, a 401/403, and a log line. Operators find it. 2. **Attestation *omission*** — a hop forwards the request without the AAIF header at all, typically because an intermediate agent framework serialized only the body and a subset of headers, or because a retry path reconstructed the request from a parsed object that never carried the attestation. This is silent. The downstream service sees a request with *no* attestation, and the question of what it does next is where the boundary failure lives. The non-obvious invariant: **in most AAIF deployments, "no attestation present" is not treated as "unauthenticated." It is treated as "attestation not required for this path," and the request is admitted under the service's default principal.** Omission therefore *elevates* rather than denies. The chain does not break; it silently rebinds the request to a more privileged or less accountable identity. ## What Was Observed In a three-hop chain (orchestrator → planner agent → tool-executor agent → resource service), the planner agent used an internal HTTP client that rebuilt outbound requests from a typed request object. That object modeled `body`, `content-type`, and a small allowlist of tracing headers. The AAIF attestation header (`X-AAIF-Attestation`) was not in the allowlist and was dropped on the planner→executor hop. The executor, a generic delegation proxy, received no attestation. Its authorization middleware was written as: - if attestation present and valid → authorize as the attested subject, enforce subject-scoped policy; - if attestation present and invalid → reject (fail closed, correct); - if attestation absent → fall through to the service-to-service mTLS principal and authorize as the *executor's own* service identity. Because the executor's service identity held broad read/write scope on the resource service (it was provisioned for batch maintenance jobs), every delegated request that lost its attestation was executed with the executor's privileges, not the originating user's. The observable symptom was not an error. It was a set of resource mutations attributed to `svc-tool-executor` in the audit log instead of to the human or agent principal that initiated the chain, and a policy evaluation that passed checks the originating principal would have failed. The failure was detected only by an audit reconciliation job comparing the originating request's subject (recorded at the orchestrator) against the acting subject recorded at the resource service. The mismatch rate was 100% on the planner→executor path and 0% elsewhere — a clean per-hop signature. ## Condition of Validity This boundary failure is *latent* — present in configuration but not observable — under all of the following conditions, and it activates only when all hold: - The chain has ≥2 delegation hops, so there exists an intermediate that must *forward* rather than *originate* the attestation. - At least one intermediate reconstructs outbound requests from a typed/parsed representation rather than passing headers opaquely. This is the actual defect surface: any code path that enumerates headers is a potential omission site. - The receiving service distinguishes "absent" from "invalid" and treats absent as a fall-through to a default principal. If the receiver fails closed on absence, omission degrades to rejection and the failure is loud. - The default/fall-through principal has *broader* scope than the attested subject. If the service identity is narrower, omission causes over-restriction (visible as denials) rather than privilege elevation. If any one of these is false, the failure does not occur or does not silently elevate. The dangerous quadrant is specifically: reconstructing intermediate + fall-through-on-absent receiver + broad service principal. ## Decision Boundary The engineering decision is not "should we validate attestations" — that is table stakes. The decision is **what absence means**, and it must be made explicitly at every receiver and audited at every forwarder: - **Receivers:** "Absent attestation" must be a distinct, explicit, *logged* outcome — never an implicit fall-through. If a path legitimately requires no attestation, that path should be enumerated and named, and the default for unenumerated paths should be reject. Treat absence as a policy event, not a missing field. - **Forwarders:** Any component that reconstructs requests must carry the attestation by construction (opaque header passthrough, or a typed field that is non-optional in the outbound model). A header allowlist is an omission generator; a header *denylist* with attestation pinned is safer, or better, sign the outbound request over the full header set. - **Chains:** The originating principal and the acting principal at each hop must be reconcilable. If the audit records at hop N and hop M cannot be joined on a chain identifier plus subject, you cannot detect omission after the fact — you can only hope to catch it in review. The boundary to draw: **absence of attestation must never be strictly more permissive than presence of a valid one.** If your fall-through principal is broader than any attested subject, you have inverted the trust gradient, and omission becomes an escalation primitive. ## Operational Effect When this boundary is crossed, the system exhibits a specific and misleading operational profile: - **No errors, no elevated latency, no failed health checks.** The chain succeeds end to end. Dashboards stay green. - **Authorization decisions are made against the wrong principal**, so policy tests that exercise the attested path pass while the production path silently bypasses them. Test coverage on the happy path *hides* the defect. - **Audit attribution drifts** to service identities, which is easy to dismiss as "normal batch activity" until an incident review asks who authorized a specific mutation and the answer is a service account that had no business doing it. - **Blast radius equals the service principal's scope**, not the user's — typically far larger, and often including cross-tenant or administrative surfaces provisioned for operational convenience. The remediation is structural, not a patch: make attestation non-optional in the outbound request type, make absence a named rejection at every receiver, and add a chain-reconciliation audit that joins originating and acting subjects. The reconciliation job is the only reliable detector, because the failure is, by construction, invisible to every component that participates in it. ```python from __future__ import annotations from dataclasses import dataclass, field from typing import Optional class AttestationOmissionError(RuntimeError): """Raised when a delegation hop forwards a request without an AAIF attestation that the receiver requires, or when a receiver would silently fall through to a broader default principal on absence.""" @dataclass class DelegationRequest: chain_id: str subject: str # originating principal attestation: Optional[str] # X-AAIF-Attestation, may be None if dropped body: dict = field(default_factory=dict) @dataclass class ReceiverPolicy: # Paths explicitly allowed to run without attestation (must be enumerated). unauthenticated_paths: frozenset[str] = frozenset() # Scope of the fall-through service principal, if any. fallthrough_scope: frozenset[str] = frozenset() def forward_hop(req: DelegationRequest, outbound_header_allowlist: frozenset[str]) -> DelegationRequest: """Simulate a reconstructing intermediate. If the attestation header is not in the allowlist, it is silently dropped -- the defect surface.""" attestation = req.attestation if "X-AAIF-Attestation" in outbound_header_allowlist else None return DelegationRequest( chain_id=req.chain_id, subject=req.subject, attestation=attestation, body=dict(req.body), ) def authorize(req: DelegationRequest, path: str, policy: ReceiverPolicy, required_scope: frozenset[str]) -> str: """Return the acting principal. Raise on any silent-elevation condition.""" if req.attestation is None: if path in policy.unauthenticated_paths: return "anonymous-enumerated" # Absence on a non-enumerated path is a policy event, never a fall-through # to a broader principal. Fail closed and loudly. if policy.fallthrough_scope >= required_scope and required_scope: raise AttestationOmissionError( f"chain {req.chain_id}: attestation absent on path {path!r}; " f"fall-through principal scope {sorted(policy.fallthrough_scope)} " f"would satisfy required scope {sorted(required_scope)} -- " f"silent privilege elevation from subject {req.subject!r}" ) raise AttestationOmissionError( f"chain {req.chain_id}: attestation absent on non-enumerated path {path!r}" ) # Attestation present: authorize as the attested subject (validation elided). return req.subject def run_chain() -> None: req = DelegationRequest(chain_id="c-42", subject="agent:user-7", attestation="aaif.v1.sig...", body={"op": "write"}) # Intermediate drops the attestation because it is not in the allowlist. forwarded = forward_hop(req, outbound_header_allowlist=frozenset({"content-type"})) policy = ReceiverPolicy(fallthrough_scope=frozenset({"read", "write", "admin"})) # This raises: absence would have been admitted as the broad service principal. authorize(forwarded, path="/resource/write", policy=policy, required_scope=frozenset({"write"})) if __name__ == "__main__": run_chain() ```