# What happens when an instance trusts a passport anchor that has been revoked in a sibling trust domain **Date of Record:** 2026 November 7 **Scope:** Applies to federated agent runtimes that validate verifiable agent passports (VAPs) against per-org trust anchors and propagate revocation via delta-CRL / status-list gossip across organizational boundaries. Does not apply to single-issuer deployments where the verifier and issuer share one revocation store, nor to purely capability-token models with no passport indirection. ## Core Distinction The failure mode is not "revocation was missed." It is the distinction between **anchor validity** and **anchor liveness across domains**. A passport is signed by an issuing org (the *home domain*), but the verifier belongs to a relying org (the *relying domain*). The relying domain caches the home domain's trust anchor and status-list head. When the home domain revokes a passport, the revocation event is authoritative at the issuer, but the relying domain's *view of the anchor* can remain structurally valid while being semantically stale. The non-obvious boundary: a passport whose signature chain still verifies against a cached anchor is **not** the same as a passport whose status is current. Signature validity is a static property of the certificate chain; revocation currency is a temporal property of a distributed status protocol. Verifiers that conflate "chain validates" with "credential is live" will admit revoked agents for the entire propagation latency window — and, worse, indefinitely if the status-list head never advances because the relying domain's gossip peer set partitions from the home domain. ## What Was Observed During a cross-org federation incident, an agent passport issued by Org A was revoked at 14:02:11 UTC after a credential-compromise signal. Org B's relying-domain verifier continued to admit the agent until 14:19:40 UTC — a 1,049-second window. The passport's signature chain validated perfectly throughout, because the issuing CA was not compromised; only the leaf credential's status changed. Root cause decomposition: 1. **Status-list head pinning.** Org B cached Org A's status-list head with a 900-second TTL and no "must-staple freshness" requirement. Between refreshes, any passport whose serial was added to the list after the pinned head remained "not revoked" from B's perspective. 2. **Gossip partition.** The delta-CRL gossip channel between A and B ran over a dedicated mesh whose liveness probe was tied to the *anchor* endpoint, not the *status* endpoint. The anchor endpoint stayed up (it served the CA cert), so the mesh was reported healthy while the status sub-channel had silently degraded to a stale mirror. 3. **No revocation epoch in the passport.** The VAP carried `issued_at` and `expires_at` but no `min_status_epoch` field, so the verifier had no way to know it was operating on a head older than the credential's issuance-adjacent revocation window. The combined effect: three independent staleness sources (TTL, partition, missing epoch binding) that each looked benign in isolation. ## Condition of Validity The invariant that *should* hold — and the condition under which the system is safe — is: > A relying domain admits a passport only if the status-list head it validates against is **not older than the passport's `min_status_epoch`** and the head's signature is from a currently-trusted anchor whose *own* revocation status is fresh within the relying domain's trust window. This decomposes into three mandatory conditions, all of which must hold simultaneously: - **C1 (epoch binding):** `status_head.epoch >= passport.min_status_epoch`. Without this, TTL-based caching is unsound because TTL bounds staleness in wall-clock time, not in revocation-sequence time. - **C2 (anchor liveness):** The anchor used to verify the status-list signature must itself have a status entry refreshed within `trust_window`, and that refresh must traverse the *status* path, not merely the anchor-serving path. - **C3 (partition detection):** The verifier must distinguish "peer reachable but stale" from "peer unreachable." A gossip peer that returns a head equal to the last-known head is ambiguous: it may mean "no new revocations" or "this mirror stopped syncing." Only a monotonically increasing epoch with signed heartbeat resolves the ambiguity. When any of C1–C3 fails, the correct behavior is **fail-closed on admission**, treating the passport as indeterminate rather than valid. ## Decision Boundary The precise boundary is between **indeterminate** and **valid**. Most implementations collapse three states into two: | Observed state | Naive verdict | Correct verdict | |---|---|---| | Chain valid, head fresh, epoch ≥ min | valid | valid | | Chain valid, head stale (TTL expired, no new head) | valid (bug) | **indeterminate → deny** | | Chain valid, head epoch < passport min | valid (bug) | **indeterminate → deny** | | Chain valid, anchor status itself stale | valid (bug) | **indeterminate → deny** | The decision boundary is *not* "is the signature good." It is "can I prove, right now, that this credential was not revoked as of an epoch at least as new as the one the issuer committed to at issuance." If that proof is unavailable, the passport is not invalid — it is unproven, and unproven must be denied for admission but logged distinctly from revocation so operators can tell a propagation failure from an actual compromise. A second, subtler boundary: **revocation propagation must be monotonic per (issuer, serial).** If a relying domain ever observes a status head whose epoch is *lower* than a previously seen head for the same issuer, that is a rollback signal (mirror corruption or replay) and must trigger a hard quarantine of the issuer's anchor, not a silent downgrade. ## Operational Effect After remediation, the relying domain's admission path enforced C1–C3 and the observed revocation-admission window collapsed from 1,049 seconds to under 40 seconds (bounded by the signed-heartbeat interval plus one gossip hop). The cost was a measurable increase in false denials during gossip jitter — roughly 0.3% of admissions during the first week — all of which were *indeterminate*, correctly surfaced to operators, and resolved by retry once the head advanced. This is the expected trade: fail-closed converts silent compromise exposure into visible, bounded availability loss. The durable lesson for federated agent runtimes: **treat the status head as a first-class trust object with its own epoch, heartbeat, and rollback detection — not as a cache annotation on the anchor.** The anchor proves *who may issue*; the status head proves *what is still live*. Conflating them is the boundary failure, and it is invisible to any verifier that checks only the signature chain. ```python from __future__ import annotations from dataclasses import dataclass from typing import Optional class FederationTrustError(RuntimeError): """Raised when a relying domain cannot prove a passport is live.""" @dataclass(frozen=True) class StatusHead: issuer: str epoch: int signed_at: float signature_valid: bool @dataclass(frozen=True) class Passport: issuer: str serial: str min_status_epoch: int chain_valid: bool @dataclass(frozen=True) class AnchorState: issuer: str status_refreshed_at: float status_path_healthy: bool def admit_passport( passport: Passport, head: Optional[StatusHead], anchor: AnchorState, last_seen_epoch: int, now: float, trust_window_s: float = 60.0, ) -> bool: """Fail-closed admission check enforcing C1, C2, C3 and rollback detection.""" if not passport.chain_valid: raise FederationTrustError( f"passport {passport.issuer}/{passport.serial}: signature chain invalid" ) # C2: anchor status must be fresh via the STATUS path, not the anchor path. if not anchor.status_path_healthy: raise FederationTrustError( f"anchor {anchor.issuer}: status path unhealthy; cannot establish liveness" ) if now - anchor.status_refreshed_at > trust_window_s: raise FederationTrustError( f"anchor {anchor.issuer}: status stale by " f"{now - anchor.status_refreshed_at:.1f}s > trust_window {trust_window_s}s" ) # C3: a missing head is indeterminate, never "not revoked". if head is None: raise FederationTrustError( f"passport {passport.issuer}/{passport.serial}: no status head available" ) if not head.signature_valid: raise FederationTrustError( f"status head for {head.issuer}: signature invalid" ) # Rollback detection: epoch must be monotonic per issuer. if head.epoch < last_seen_epoch: raise FederationTrustError( f"status rollback for {head.issuer}: epoch {head.epoch} < " f"previously seen {last_seen_epoch}; quarantine anchor" ) # C1: epoch binding between passport and head. if head.epoch < passport.min_status_epoch: raise FederationTrustError( f"passport {passport.issuer}/{passport.serial}: head epoch " f"{head.epoch} < min_status_epoch {passport.min_status_epoch}; " f"indeterminate, deny admission" ) return True ```