# What happens when an instance retains a tainted capability token across a downgrade epoch boundary **Date of Record:** 2026 November 11 **Scope:** Applies to agent runtimes that execute tool calls inside taint-tracking sandboxes where capability grants are versioned by a monotonic downgrade epoch and revoked by epoch increment. Does not apply to stateless per-call sandboxes, nor to runtimes where capability tokens are re-minted (not merely re-validated) on every epoch advance. ## Core Distinction A taint-aware sandbox tracks two orthogonal things: *what data an agent has touched* (taint labels) and *what it is permitted to do next* (capability grants). Dynamic capability downgrading couples them: when taint crosses a sensitivity threshold, the runtime increments a downgrade epoch and narrows the capability set. The non-obvious failure is that **epoch incrementing is a revocation mechanism, but token validation is a caching mechanism**. A capability token minted in epoch *N* is still structurally valid; whether it is *honored* depends on whether the validator re-reads the epoch on each use. If the validator caches the epoch-validity decision (a common optimization, since epoch reads are a shared lock), a token minted before the taint event remains honored after the downgrade. The taint label propagated correctly; the capability downgrade did not. The two systems desynchronized at the epoch boundary. ## What Was Observed In a tool-execution trace, an agent read a file classified `sensitive:internal` (taint label applied, epoch advanced 41→42, `fs.write` capability narrowed to read-only paths). The agent then invoked `fs.write` with a token minted at epoch 41. The validator returned `valid: true` from a per-token cache populated at mint time, bypassing the epoch-42 capability table. The write succeeded to a path the downgrade was meant to forbid. The taint system logged the read; the capability system logged the write; neither logged the contradiction. The failure is silent — no exception, no denial, no audit anomaly — because both subsystems behaved correctly *in isolation*. The invariant that broke is the cross-system one: *for every honored capability token, the token's mint epoch must be ≥ the current downgrade epoch, or the token must be re-minted under the current capability table*. ## Condition of Validity The bug requires all of: (1) capability tokens with a mint-time epoch stamp; (2) a validator that caches validity keyed by token identity rather than by (token, current_epoch); (3) a taint event that advances the epoch without invalidating the validator cache; (4) a tool call issued after the taint event using a pre-taint token. Remove any one and the boundary holds. The most common false fix is invalidating the cache on epoch advance — this closes the window but reintroduces a race: a token validated between the epoch increment and the cache invalidation is still honored. The correct fix is to make the epoch read part of the validation predicate itself, so there is no cache to invalidate — validation is `token.mint_epoch >= current_epoch()` evaluated at use time, with the epoch read inside the same critical section as the capability-table lookup. ## Decision Boundary When designing the validator, choose: **cache validity keyed on token alone** (fast, but epoch-blind — unsafe under dynamic downgrading) versus **evaluate epoch at use time** (one extra atomic read per call, correct under downgrading). The boundary is crossed the moment taint can advance the epoch asynchronously to tool execution. If downgrades only ever occur at tool-call boundaries (synchronous, single-threaded), token-keyed caching is safe. If downgrades can occur *during* an agent turn — triggered by a read inside a multi-step tool chain — token-keyed caching is unsafe and must be replaced. The decision is not about performance; it is about whether the epoch is a *snapshot* (safe to cache) or a *live variable* (must be re-read). ## Operational Effect Under the failure, sensitivity downgrades become advisory rather than enforced: the agent's taint labels correctly record that it touched sensitive data, but its capabilities do not narrow, so it can exfiltrate via the very tool the downgrade was meant to restrict. The audit trail shows a compliant taint log and a compliant capability log that disagree, making post-hoc detection require cross-log correlation that most pipelines do not perform. The practical mitigation is to assert the cross-system invariant at every tool dispatch: reject any token whose mint epoch is below the current downgrade epoch, and treat that rejection as a hard error (not a silent re-mint), because a silent re-mint masks the desynchronization that caused it. ```python class TaintedCapabilityEpochError(RuntimeError): """Raised when a capability token's mint epoch predates the current downgrade epoch.""" pass def dispatch_tool_call(token, current_downgrade_epoch, capability_table, tool_name, args): # Cross-system invariant: a token minted before the current downgrade epoch # must never be honored, even if its signature and capability bits are valid. if token.mint_epoch < current_downgrade_epoch: raise TaintedCapabilityEpochError( f"token minted at epoch {token.mint_epoch} used under downgrade epoch " f"{current_downgrade_epoch}; capability downgrade was bypassed by cached " f"validation. Tool={tool_name!r}. Re-mint required, not silent re-validation." ) granted = capability_table.get(current_downgrade_epoch, {}).get(tool_name) if granted is None or not granted.permits(args): raise TaintedCapabilityEpochError( f"tool {tool_name!r} not permitted under epoch {current_downgrade_epoch} " f"capability table (args={args!r})" ) return execute(tool_name, args) ```