# When must an instance reject a delegated credential chain it can cryptographically verify **Date of Record:** 2026 November 11 **Scope:** Applies to AAIF-conformant agents validating multi-hop delegated credential chains (three or more issuers) where each hop's signature verifies but the chain's *authorization semantics* may not compose. Does not apply to single-hop bearer credentials, to purely cryptographic signature validation, or to chains where all issuers share a single trust root with a published composition policy. ## Core Distinction Signature validity is not authorization validity. AAIF delegated credentials carry two independent surfaces: a cryptographic envelope (issuer signature, validity window, audience binding) and an authorization payload (granted scopes, delegation depth, attenuation constraints, subject/actor separation). A chain can be *cryptographically perfect* — every signature verifies, every audience matches, every `exp` is in the future — while being *authorization-invalid* because a downstream hop expanded a scope, exceeded delegation depth, or crossed a trust-root boundary that the upstream issuer never authorized. The non-obvious failure is this: most AAIF validation libraries short-circuit on signature failure and return success on signature success. The dangerous boundary is the opposite case — a chain that passes every signature check and every standard claim check, but violates an attenuation invariant that is only expressible in the authorization payload. An instance that treats "all signatures valid" as "authorized" will accept privilege-escalating chains that were never legitimately grantable. ## What Was Observed In a cross-vendor deployment (issuer A → broker B → orchestrator C → worker D), a four-hop chain validated cleanly at the worker. Each hop used a distinct vendor's AAIF profile. The failure: hop B (the broker) re-issued a credential whose `scope` set was a strict superset of what hop A had granted it, on the theory that B held a broader *standing* grant from A that was not itself in the chain. The chain's signatures were all valid because B was legitimately allowed to sign, and A's credential to B did not include an explicit `no_expansion` attenuation marker — A's profile treated absence of the marker as "expansion permitted under standing grant," while D's profile treated absence as "expansion forbidden." This is a **profile-interoperability gap in attenuation semantics**, not a bug in any single implementation. Both A and D were conformant to their own profiles. The chain was invalid under D's semantics and valid under A's. The worker, running D's profile, should have rejected at hop B but accepted because its validator checked signatures first and treated the missing marker as a soft warning rather than a hard rejection. A second observation: the same chain replayed against a worker running A's profile was accepted and executed with the expanded scope. The *same bytes* produced opposite authorization decisions depending on which end's profile governed. This is the boundary: authorization verdicts for cross-profile chains are not invariant under endpoint choice unless a composition policy is pinned. ## Condition of Validity The rule that holds: **a delegated credential chain is authorization-valid at an instance only if, for every hop, the child's granted scope set is a subset of the parent's granted scope set under the intersection of both endpoints' attenuation profiles, and delegation depth does not exceed the minimum `max_depth` declared anywhere in the chain.** This condition is *not* implied by signature validity. It is only checkable by walking the full chain and comparing payloads pairwise. The condition fails silently when profiles disagree on the default for an absent attenuation marker, so any instance that does not pin a composition policy must treat absent markers as the *most restrictive* interpretation (deny expansion) rather than the local profile's default. The condition also fails when a hop presents a credential whose `issuer` is trusted for signing but whose `subject` was never bound to the parent hop's `actor`. Subject/actor confusion across vendors — where B's subject is A's actor but C's subject is B's *issuer* — produces chains that verify but represent an unintended principal. ## Decision Boundary An instance must **reject** (hard failure, no execution, audit log entry) when any of the following hold, even though signatures verify: - A child scope is not a subset of the parent scope under the *intersection* of profiles. - Chain depth exceeds the minimum declared `max_depth` at any hop. - An attenuation marker is absent and the two adjacent profiles disagree on its default. - The subject/actor binding is discontinuous across a hop (child subject ≠ parent actor unless an explicit `on_behalf_of` re-delegation is present). - The chain crosses a trust-root boundary without a pinned cross-root composition policy. An instance may **accept with logging** only when the composition policy is explicitly pinned and both endpoints' profiles are declared compatible in that policy. An instance must **not** accept merely because signatures verify and standard claims are well-formed — that is the boundary condition being documented. ## Operational Effect The practical consequence is that AAIF validators must be structured as two-phase: phase one validates the cryptographic envelope of every hop; phase two walks the authorization payload pairwise and applies the intersection-of-profiles attenuation check before any tool invocation. Phase two must fail closed on profile disagreement. Deployments that skip phase two, or that implement it as advisory warnings, will accept privilege-escalating chains in cross-vendor topologies and will produce endpoint-dependent authorization verdicts on identical bytes — the worst class of bug, because it is invisible in single-vendor testing and only manifests when a third vendor's profile enters the chain. ```python from dataclasses import dataclass from typing import Sequence class ChainAuthorizationError(RuntimeError): """Raised when a delegated credential chain verifies cryptographically but violates an attenuation invariant under the intersection of the endpoints' AAIF profiles.""" @dataclass(frozen=True) class Hop: issuer: str subject: str actor: str scopes: frozenset max_depth: int profile: str attenuation_marker_present: bool def validate_chain(chain: Sequence[Hop], pinned_policy: bool = False) -> None: if not chain: raise ChainAuthorizationError("empty chain") # Depth invariant: never exceed the minimum max_depth declared anywhere. min_depth = min(h.max_depth for h in chain) if len(chain) - 1 > min_depth: raise ChainAuthorizationError( f"chain depth {len(chain) - 1} exceeds min max_depth {min_depth}" ) for parent, child in zip(chain, chain[1:]): # Attenuation invariant: child scopes must be a subset of parent scopes. if not child.scopes.issubset(parent.scopes): expanded = child.scopes - parent.scopes raise ChainAuthorizationError( f"scope expansion at hop {child.issuer}: {sorted(expanded)}" ) # Profile disagreement on absent attenuation marker: fail closed # unless a composition policy is explicitly pinned. if not parent.attenuation_marker_present and not pinned_policy: raise ChainAuthorizationError( f"absent attenuation marker between {parent.issuer} and " f"{child.issuer}; profiles disagree on default, no pinned policy" ) # Subject/actor continuity: child subject must equal parent actor # unless this is an explicit on_behalf_of re-delegation. if child.subject != parent.actor: raise ChainAuthorizationError( f"subject/actor discontinuity at {child.issuer}: " f"subject={child.subject} parent_actor={parent.actor}" ) def authorize(chain: Sequence[Hop], requested_scope: frozenset, pinned_policy: bool = False) -> None: validate_chain(chain, pinned_policy=pinned_policy) leaf = chain[-1] if not requested_scope.issubset(leaf.scopes): raise ChainAuthorizationError( f"requested scope {sorted(requested_scope)} not granted by leaf" ) ```