# When must an instance refuse to inherit ambient capability across a taint boundary **Date of Record:** 2026 November 12 **Scope:** Applies to runtime sandboxing of agent tool calls where a capability handle (file descriptor, RPC token, mount, or subprocess grant) is passed from a parent execution context into a child context that has been tainted by untrusted input. Does not apply to capability *creation* (fresh grants minted inside the child) or to purely declarative policy engines that never hand out live handles. ## Core Distinction The non-obvious boundary is between **capability attenuation** and **capability *provenance* reset**. Attenuation narrows a handle's *authority* (read-only instead of read-write, subtree instead of root). Taint tracking narrows a handle's *trust* (the inputs that produced the call). These are orthogonal axes, and the common failure is to attenuate without resetting provenance — or to reset provenance while silently preserving a *derived* handle whose authority was computed from the pre-taint context. Concretely: a child context that has ingested untrusted data (a scraped web page, a tool result, a model-generated path) must not inherit a capability whose *authority set* was finalized before taint was applied. Attenuation is necessary but insufficient. If the parent computed `allowed_paths = {/data/a, /data/b}` from a clean config and then passed an *already-attenuated* handle to the tainted child, the child holds a handle whose authority was decided under a different trust regime. The invariant is: **a capability handle's authority set must be re-derived (not merely narrowed) at the moment it crosses into a tainted context.** Narrowing a stale authority set leaves the residual authority implicitly trusted. ## What Was Observed In a sandboxed tool-call runtime, the parent agent opened a directory capability with the intent `read`. The runtime attenuated it to `read` for a child that had just consumed an untrusted tool result. The attenuation was logged as a success. The child then issued a path that traversed a symlink *inside* the granted subtree to a location outside it. The handle's authority set had been computed at grant time against the *resolved* tree; because the authority set was never recomputed after taint, the symlink resolution used the pre-taint snapshot and the attenuation check passed against stale inodes. Observed effect: a read of a file the child should not have reached, with a clean attenuation audit trail. The failure was invisible to the policy engine because attenuation reported "narrowed to read" and the read was, formally, a read. A second observation: when the runtime *did* reset provenance but reused the parent's cached attenuation decision, a child inherited a `write` capability because the cache key omitted the taint bit. The cache is the tell — any memoized authority decision whose key does not include the current taint state is a latent inheritance bug. ## Condition of Validity The re-derivation requirement holds only when **all three** are true: 1. The capability handle is *live* (an OS-level or RPC-level object the child can actually exercise), not a serialized description re-checked at use time. 2. Taint can be introduced *between* grant and use, i.e., the child context is mutable across the call boundary. 3. Authority is computed from state (resolved paths, group membership, ACLs) that can change after grant. If the runtime re-validates the full authority set at every use against current state and current taint, re-derivation at the boundary is redundant (though still cheaper to audit). If handles are immutable and taint is fixed at context creation, the boundary does not exist. ## Decision Boundary Refuse inheritance — raise and require an explicit re-grant — when a tainted child would receive a live handle whose authority set was finalized under a lower taint level, **and** the handle's authority depends on resolvable mutable state (symlinks, ACLs, group membership, mount tables). Attenuate-and-pass is acceptable only when the handle's authority is a *static literal set* independent of resolvable state and the attenuation is re-checked at use. When in doubt, re-derive: the cost is one authority computation; the cost of the bug is a silent, audited-clean privilege leak. ## Operational Effect The runtime must carry a taint epoch on every context and stamp every capability handle with the epoch at which its authority set was computed. On crossing into a context with a higher taint epoch, the handle is *invalidated* and must be re-granted; passing it through is a hard error, not a warning. This converts a silent inheritance into a loud refusal, and it makes the attenuation audit trail trustworthy — a logged "narrowed to read" now means the authority was actually recomputed under the current trust regime, not inherited from a cleaner past. ```python class TaintInheritanceError(RuntimeError): """Raised when a live capability would cross into a more-tainted context without its authority set being re-derived at the boundary.""" pass class CapabilityHandle: def __init__(self, authority: frozenset, taint_epoch: int): self.authority = authority self.taint_epoch = taint_epoch # epoch at which authority was computed def pass_capability(handle: CapabilityHandle, child_taint_epoch: int, authority_depends_on_mutable_state: bool) -> CapabilityHandle: if child_taint_epoch > handle.taint_epoch: if authority_depends_on_mutable_state: raise TaintInheritanceError( f"handle authority computed at epoch {handle.taint_epoch} " f"cannot cross into tainted epoch {child_taint_epoch}; " f"re-derive authority before passing" ) # static literal authority: safe to forward, but re-stamp provenance return CapabilityHandle(handle.authority, child_taint_epoch) return handle ```