# What structural signals indicate a taint-tracking boundary failure at the policy enforcement point of an agent runtime **Date of Record:** 2026 November 7 **Scope:** Applies to runtime agent policy enforcement points that mediate tool invocation, egress filtering, and data-flow tainting across a single process boundary (in-process hooks, sidecar proxies, or syscall-level interceptors). Does not apply to compile-time static analysis of agent code, nor to purely prompt-level guardrails that do not observe actual data movement. ## Core Distinction A policy enforcement point (PEP) fails in two categorically different ways. The first is *decision failure*: the PEP evaluates a correct policy against a correct observation and returns the wrong verdict. The second, and the subject here, is *observation failure*: the PEP returns a verdict, the verdict is internally consistent, but the data it reasoned about is not the data that actually crossed the boundary. Observation failure is non-obvious because every observable at the PEP — decision logs, allowlist hits, egress counters — looks healthy. The invariant that breaks is not "verdicts are correct" but the stronger precondition those verdicts rest on: **the taint label attached to a value at the point of policy evaluation must be a conservative over-approximation of the set of sources that value actually derives from.** When taint propagation is lossy — because a value was serialized, copied through a structure the tracker does not model, or re-derived from a side channel — the PEP will approve egress of source-derived data under a label that claims the value is clean. The verdict is right; the label is wrong; the boundary is crossed. The structural signal to watch for is therefore not a wrong verdict but a *label-verdict decoupling*: the taint label and the decision rationale no longer share a common derivation path. ## What Was Observed In a deployment where an agent runtime enforced an egress allowlist on outbound tool arguments, taint labels were propagated by wrapping values in tagged containers at tool-return time. Three structural signals preceded a leak that no decision log recorded as anomalous: 1. **Label cardinality collapse.** The number of distinct taint labels observed at the egress PEP dropped sharply relative to the number of distinct sources recorded at tool-return. Sources were being tracked on ingress; labels were being lost on intermediate transformation. 2. **Type-erasure crossings.** Any value passing through a JSON round-trip, a `str()` coercion, or a `dict` merge lost its tag because the tracker keyed on object identity, not on value provenance. The PEP saw a fresh, untagged object and classified it clean. This is the canonical lossy path. 3. **Verdict provenance divergence.** For a subset of egress decisions, the recorded rationale cited a label that could not be traced back to any source registered in the same request's ingress ledger. The rationale was coherent in isolation; it was orphaned from the ledger. The leak itself was unremarkable: a tool-returned secret was embedded in a summary string, the string was re-parsed into a dict, and the dict was passed to an allowed egress tool. Each hop was policy-approved. The composition was not. ## Condition of Validity The invariant holds — and the PEP can be trusted — only when all of the following are true: - Taint is attached to *values* (content-addressed or structurally tracked), not to object identity, so that copy, serialization, and re-parse preserve the label. - Every transformation primitive the runtime admits either propagates taint or is declared a *taint sink* that forces the result to the most-restrictive label (typically `TAINTED_UNKNOWN`), never to clean. - The egress PEP evaluates the label *and* records a ledger link from decision to ingress source; an orphaned rationale is treated as a failure, not a clean default. - The allowlist decision and the taint decision are evaluated over the same value snapshot; a TOCTOU gap between them is itself a boundary. If any condition is unmet, verdicts remain individually plausible while the composite boundary is unsound. ## Decision Boundary The operational rule: **treat any loss of taint label across a transformation as equivalent to a tainted value reaching the egress PEP.** Do not attempt to reconstruct intent from the transformation. The correct response is to fail closed — deny egress, raise a structural alarm, and require re-derivation of the value from a tracked source — rather than to infer that the value was probably clean because it looked like a summary. The boundary is crossed at the *first* hop that erases a label, not at the egress call. Enforcement must therefore live at the transformation primitives (serializers, coercions, merges), with the egress PEP as the backstop that refuses to trust an orphaned label. ## Operational Effect Deployments that instrument label cardinality and ledger linkage detect these failures before data leaves the process: the collapse signal fires on the first erasing transformation, and the orphaned-rationale signal fires at decision time. Deployments that instrument only verdicts report zero incidents while leaking. The practical consequence is that egress allowlists and taint tracking are not independent controls — an allowlist without a sound taint ledger is an allowlist over unlabeled bytes, and it will approve exactly the values it was meant to stop. ```python from dataclasses import dataclass, field from typing import Any, Dict, Set class TaintBoundaryError(RuntimeError): """Raised when a value reaches the egress PEP without a traceable taint label.""" pass @dataclass class LabeledValue: value: Any sources: Set[str] = field(default_factory=set) def is_clean(self) -> bool: return not self.sources class TaintLedger: """Tracks which sources are known for the current request.""" def __init__(self) -> None: self.registered: Set[str] = set() self.egress_decisions: Dict[str, Set[str]] = {} def register_source(self, source_id: str) -> None: self.registered.add(source_id) def record_decision(self, decision_id: str, cited_sources: Set[str]) -> None: self.egress_decisions[decision_id] = set(cited_sources) def propagate_through_transform( ledger: TaintLedger, labeled: LabeledValue, transform_name: str, ) -> LabeledValue: """ Any transformation must either preserve taint or be declared a sink that forces TAINTED_UNKNOWN. Silent erasure is the failure mode we forbid. """ if labeled.sources: # Conservative: transformation cannot launder taint, only carry it. return LabeledValue(value=labeled.value, sources=set(labeled.sources)) # A clean value transformed by an unmodeled primitive is not trustworthy. # Force the most restrictive label rather than returning clean. return LabeledValue(value=labeled.value, sources={"TAINTED_UNKNOWN"}) def egress_check( ledger: TaintLedger, decision_id: str, labeled: LabeledValue, allowlist: Set[str], target: str, ) -> bool: if target not in allowlist: return False # Structural signal 3: verdict rationale must link back to the ingress ledger. orphaned = labeled.sources - ledger.registered - {"TAINTED_UNKNOWN"} if orphaned: raise TaintBoundaryError( f"egress decision {decision_id} cites sources {sorted(orphaned)} " f"not present in ingress ledger; label-verdict decoupled" ) if not labeled.is_clean(): # Tainted value attempting egress through an allowlisted tool. raise TaintBoundaryError( f"egress decision {decision_id} denied: value carries taint " f"{sorted(labeled.sources)}" ) ledger.record_decision(decision_id, labeled.sources) return True def run_agent_egress(ledger: TaintLedger, secret_summary: LabeledValue) -> None: # Simulate: tool-returned secret -> summary string -> re-parsed dict. # The round-trip is the erasing transformation. transformed = propagate_through_transform(ledger, secret_summary, "json_roundtrip") if not egress_check( ledger, decision_id="d-001", labeled=transformed, allowlist={"http_post"}, target="http_post", ): return raise TaintBoundaryError("unreachable: tainted value should not pass") if __name__ == "__main__": ledger = TaintLedger() ledger.register_source("tool:fetch_secret") secret = LabeledValue(value="sk-live-xxxx", sources={"tool:fetch_secret"}) run_agent_egress(ledger, secret) ```