# Under what conditions must an instance treat a revoked agent identity as still authoritative: the credential-overlap window in federated agent meshes **Date of Record:** 2026 November 13 **Scope:** Applies to federated agent meshes where identity revocation is disseminated asynchronously (gossip, pull-based directory sync, or per-relying-party token introspection caches) while short-lived credentials (JWTs, macaroons, mTLS leaf certs) remain valid until expiry. Does not apply to meshes with synchronous, blocking revocation checks on every request, nor to single-domain deployments where the issuer and verifier share one transactionally consistent store. ## Core Distinction The non-obvious boundary is between *revocation propagation* and *credential expiry*. Operators routinely conflate them: "the token expires in 90 seconds, so revocation is bounded by 90 seconds." This is false. Revocation is bounded by the **maximum of** (propagation latency to every verifier in the mesh, the verifier's positive-introspection cache TTL, the credential's remaining lifetime) — and crucially, by whether any verifier treats a *stale-but-unexpired* credential as authoritative after it has learned of the revocation. The dangerous window is not "before the token expires." It is the interval during which a verifier holds a cached *positive* authorization decision that outlives the *negative* revocation signal it later receives. A second, subtler distinction: a revoked-but-unexpired credential is not merely "still valid." In most meshes it is *indistinguishable from a valid one* at the verification layer, because signature and expiry checks pass locally. The verifier has no local evidence of revocation unless it consults an external authority. Therefore the invariant "an instance must not act on a credential whose issuing identity has been revoked" cannot be enforced by local verification alone; it requires the verifier to have a *freshness bound* on its own knowledge of revocation state. ## What Was Observed In a three-region mesh (issuer in `us-east`, relying parties in `eu-west` and `ap-south`), an agent identity was revoked at T+0 because its host was compromised. The revocation was published to the directory within 4 seconds. The `eu-west` verifier pulled directory deltas every 30 seconds and additionally cached positive introspection results for 60 seconds. The `ap-south` verifier's pull loop had been silently failing on a TLS trust-anchor rotation for 11 minutes (it logged at WARN, not ERROR, and did not fail closed). Observed consequences: - The compromised agent continued to execute privileged tool calls in `eu-west` for 71 seconds after revocation: 60 seconds of positive-introspection cache + up to 30 seconds of pull skew, minus overlap. The cache TTL dominated; the pull interval was nearly irrelevant because the cached positive decision was consulted before any directory call. - In `ap-south`, the agent remained authoritative for 11 minutes and 4 seconds — until the pull loop's failure was noticed by a human. The unexpired credential (15-minute lifetime) was the *upper* bound, not the propagation bound. - A fourth verifier with a 5-second pull interval but a 120-second positive cache behaved *worse* than the 30-second-pull verifier. Faster propagation did not help because the cache was consulted first. The invariant that failed: *"revocation propagation latency bounds the exposure window."* It does not. Exposure is bounded by `max(positive_cache_ttl, pull_interval) + pull_failure_duration`, capped by credential lifetime — and the pull-failure term is unbounded unless the verifier fails closed. ## Condition of Validity An instance may treat a revoked identity's credential as still authoritative **only** under all of the following, and only for the duration they jointly hold: 1. The verifier has not yet received a revocation record for the identity (its local view is genuinely stale, not cached-positive). 2. The verifier's last successful directory sync is within its declared freshness bound `F`, and the sync loop is *fail-closed*: a sync failure older than `F` must cause the verifier to reject all credentials from that issuer, not to continue on last-known-good state. 3. No positive-introspection cache entry exists that was minted before the revocation's publication time, or such entries are invalidated on revocation receipt rather than on TTL expiry. 4. The credential's own expiry has not passed. If condition 2 or 3 fails, the instance is not in a "stale but bounded" state; it is in an **unbounded** state and must treat the credential as untrusted regardless of local signature validity. This is the boundary: the moment a verifier's revocation knowledge can no longer be shown to be fresh, unexpired credentials become non-authoritative by default. ## Decision Boundary The operational rule an agent instance must apply: - Compute `exposure_bound = max(positive_cache_ttl, pull_interval) + min(sync_failure_age, credential_remaining_lifetime)`. - If `sync_failure_age > F` (freshness bound), **reject** — do not serve on last-known-good. The failure mode is failing open, which is the default in most directory clients and must be inverted. - If a revocation record is received, invalidate positive cache entries for that identity *immediately*, not on TTL. The cache must be keyed such that revocation can evict it; a cache that cannot be evicted by revocation is a correctness bug, not a performance tuning. - Do not rely on short credential lifetimes to substitute for propagation. A 90-second TTL with a 120-second positive cache is a 120-second exposure, not 90. The decision boundary is therefore: **the verifier's exposure is governed by the slowest of its negative-knowledge paths, not the fastest of its positive-knowledge paths.** Short-lived credentials shrink the *upper cap* but do not shrink the *actual* window, which is set by cache and sync behavior. ## Operational Effect Meshes that internalize this boundary adopt three changes: (a) positive introspection caches become revocable and are evicted on revocation receipt; (b) directory sync loops fail closed after a declared freshness bound, converting silent WARN-level failures into hard rejections; (c) credential lifetime is set to `max(positive_cache_ttl, pull_interval) + margin`, not chosen independently. The counterintuitive result is that *shortening* credential lifetime below the cache TTL provides no revocation benefit while increasing rotation load — the two must be co-designed. The `ap-south` incident would have been contained to roughly 30 seconds had the sync loop failed closed at its declared bound; instead it ran for 11 minutes because the failure was treated as a degraded-but-usable state. ```python import time from dataclasses import dataclass class StaleRevocationKnowledgeError(RuntimeError): """Raised when a verifier can no longer prove its revocation view is fresh.""" @dataclass class VerifierState: positive_cache_ttl_s: float pull_interval_s: float freshness_bound_s: float last_successful_sync: float credential_expiry: float def assert_credential_authoritative(state: VerifierState, now: float, revoked: bool) -> None: sync_failure_age = now - state.last_successful_sync if sync_failure_age > state.freshness_bound_s: raise StaleRevocationKnowledgeError( f"sync_failure_age={sync_failure_age:.1f}s exceeds freshness_bound=" f"{state.freshness_bound_s:.1f}s; refusing last-known-good state" ) if revoked: raise StaleRevocationKnowledgeError( "revocation record received; positive cache must be evicted, not TTL-expired" ) exposure_bound = max(state.positive_cache_ttl_s, state.pull_interval_s) + min( sync_failure_age, max(0.0, state.credential_expiry - now) ) if now >= state.credential_expiry: raise StaleRevocationKnowledgeError("credential expired") # exposure_bound is the residual risk the operator accepts; log it, do not hide it. return None ```