# Which invariants prevent capability-scoped tool invocation from degrading into ambient authority under mid-execution policy reload **Date of Record:** 2026 November 9 **Scope:** Applies to runtime enforcement layers that mediate tool/function invocation through capability tokens, where policy can be reloaded while a tool call is in flight. Does not apply to statically compiled policy (where the policy image is immutable for the process lifetime), nor to sandboxes that revoke the entire process on any policy change. ## Core Distinction The distinction that matters is between **authority** (what a token *permits*) and **attenuation state** (what the currently loaded policy *admits*). A capability token is a bearer credential: possession implies the right to invoke. A runtime policy is a predicate evaluated at the mediation point. The failure mode is not "policy is stale" — that is expected and bounded. The failure is **policy reload racing the mediation check**, producing a state where a token issued under policy generation *G* is evaluated against generation *G+1* whose authority lattice is not a subset of *G*'s. When the new policy is *broader* than the old one, a token minted before reload silently acquires permissions it was never scoped to. The token was never reissued; its `cap_id` is unchanged; but the effective authority has grown. This is ambient authority re-entering through a reload window. A second, subtler distinction: **revocation** and **attenuation** are not the same operation. Revoking a capability means the token no longer authorizes anything. Attenuating means the token authorizes a strictly smaller set. Reloading policy can do either, or — the dangerous case — neither, by changing the *interpretation* of the token's scope set without touching the token itself. ## What Was Observed In a mediation layer where each tool invocation carries a signed capability manifest (`{tool, resource, verb, constraints, gen}`) and the runtime holds a policy document mapping `(tool, resource, verb)` triples to allow/deny plus constraint predicates, the following sequence was reproducible: 1. Agent requests `fs.write` on `/data/out/report.csv` under generation 7. Manifest admits it; check passes. 2. Between the admission check and the actual syscall dispatch, an operator reloads policy to generation 8. Generation 8 adds a new constraint predicate that *widens* the resource glob from `/data/out/*` to `/data/*` (a misconfiguration during a "fix"). It also reorders constraint evaluation. 3. The in-flight call completes against generation 8's widened glob because the enforcement point re-resolves the resource pattern at dispatch time rather than binding it at check time. 4. A subsequent call from the *same* manifest, carrying `gen: 7`, is admitted against generation 8's widened glob. The manifest's generation field is recorded but not compared. The observable symptom was not a crash. It was a silent widening: an audit log showing `cap_gen=7 policy_gen=8 action=allow target=/data/out/report.csv` followed by `cap_gen=7 policy_gen=8 action=allow target=/data/secret/key.pem`, where the second line should have been `deny` under the manifest's own scope. The root cause is a **time-of-check-to-time-of-use (TOCTOU) gap on the policy image itself**, combined with the absence of a generation-compatibility invariant at the dispatch boundary. ## Condition of Validity The failure requires all of the following to hold simultaneously: - Policy is reloadable at runtime (hot reload, SIGHUP, file-watch, or control-plane push). - The mediation point resolves scope *after* the admission decision, i.e., the check and the dispatch do not share a frozen snapshot of the policy. - The capability manifest does not pin a policy generation that the runtime is required to honor, or the runtime ignores the pinned generation. - The new policy is not provably a *subset* of the old one. (If reloads are required to be monotone-attenuating, the failure cannot occur: widening is impossible by construction.) If any of these is absent, the failure does not manifest. In particular, if reloads are required to be attenuating-only — every new generation's allow-set is a subset of the previous generation's — then a token minted under *G* remains safe under *G+1* because authority only shrinks. This is the **monotone-attenuation invariant**, and it is the single most important structural defense. ## Decision Boundary The operational decision is: **at what point do you freeze the policy image for a given invocation?** Three viable postures, each with a distinct cost: 1. **Bind-at-check.** Snapshot the policy generation at the moment of admission; dispatch against that snapshot. Requires the runtime to hold the old generation alive until all in-flight calls complete (a reference-counted policy image). Cost: memory proportional to concurrent generations; reload latency bounded by the longest in-flight call. This is the correct default. 2. **Re-check-at-dispatch.** Re-evaluate the full admission predicate immediately before dispatch, using the current generation, and require that the manifest's pinned generation is *compatible* with the current one (equal, or current is a strict subset of pinned). Cost: double evaluation; rejects in-flight calls that raced a widening reload. This is the safest posture but can cause spurious denials during legitimate reloads. 3. **Monotone-attenuation-only reload.** Forbid widening reloads at the control plane. Any reload that would add an allow or relax a constraint is rejected unless it also bumps a global epoch that invalidates all outstanding manifests. Cost: operators cannot hot-widen; they must reissue manifests. This is the invariant that makes posture 1 and 2 unnecessary in the common case. The boundary condition that breaks all three if unaddressed: **constraint predicates that are not monotone in their arguments.** A predicate like `path.startswith(prefix)` is monotone in `prefix` only if `prefix` shrinks. A predicate that consults external state (a database, a time source, a feature flag) is not a pure function of the manifest and cannot be snapshot-bound. Any constraint that reads mutable external state reintroduces the TOCTOU gap regardless of policy-image pinning. The invariant must therefore be: **all constraint predicates are pure functions of `(manifest, frozen_policy_snapshot)`; external state is captured into the snapshot at reload time, not read at dispatch time.** ## Operational Effect When the monotone-attenuation invariant holds and constraint predicates are pure over the frozen snapshot, the reload window is harmless: in-flight calls complete under the generation they were admitted against, and new calls are evaluated against the new generation with the guarantee that their authority is a subset of what their manifests permit. The audit log becomes a faithful record because `cap_gen` and `policy_gen` are always compared and a mismatch that implies widening is a hard deny. When the invariant is violated, the failure is silent and the blast radius is bounded only by the difference between the two policy generations. The most dangerous case is a well-intentioned "fix" that widens a resource glob to resolve a false denial — this converts a usability bug into a privilege-escalation window for every manifest minted before the reload. The enforcement code must therefore treat a generation mismatch that is not provably attenuating as a hard error, not a warning. The following guard encodes that decision boundary: ```python from __future__ import annotations from dataclasses import dataclass from typing import FrozenSet, Tuple class PolicyGenerationError(RuntimeError): """Raised when a capability manifest cannot be safely dispatched against the currently loaded policy generation.""" @dataclass(frozen=True) class PolicySnapshot: generation: int # allow-set of (tool, resource, verb) triples; frozen at reload time allow: FrozenSet[Tuple[str, str, str]] def is_attenuation_of(self, prior: "PolicySnapshot") -> bool: """True iff every allow in self was also present in prior. Monotone-attenuation invariant: a reload may only shrink authority. Widening reloads are rejected at the control plane. """ return self.allow.issubset(prior.allow) @dataclass(frozen=True) class CapabilityManifest: cap_id: str tool: str resource: str verb: str pinned_gen: int def dispatch( manifest: CapabilityManifest, admitted_under: PolicySnapshot, current: PolicySnapshot, ) -> None: """Dispatch a tool invocation, enforcing generation compatibility. The call was admitted against `admitted_under`. Before touching the tool, we require that the current policy is either the same generation or a strict attenuation of the admitted generation. Anything else is a widening reload racing an in-flight manifest and must be refused. """ if manifest.pinned_gen != admitted_under.generation: raise PolicyGenerationError( f"manifest {manifest.cap_id} pinned gen {manifest.pinned_gen} " f"but was admitted under gen {admitted_under.generation}" ) if current.generation != admitted_under.generation: if not current.is_attenuation_of(admitted_under): raise PolicyGenerationError( f"policy reload gen {admitted_under.generation}->{current.generation} " f"is not an attenuation; refusing in-flight manifest {manifest.cap_id}" ) # Attenuation is safe: the triple must still be allowed under current. triple = (manifest.tool, manifest.resource, manifest.verb) if triple not in current.allow: raise PolicyGenerationError( f"manifest {manifest.cap_id} triple {triple} attenuated away " f"by reload to gen {current.generation}" ) triple = (manifest.tool, manifest.resource, manifest.verb) if triple not in admitted_under.allow: raise PolicyGenerationError( f"manifest {manifest.cap_id} triple {triple} not admitted " f"under gen {admitted_under.generation}" ) # dispatch to tool here, against the frozen snapshot semantics ```