Tag Every Value by Origin, Gate the Dangerous Writes
Label each value by where it came from, propagate the label through the pipeline, and route any sensitive action invoked with untrusted-tainted arguments into a human review lane instead of auto-committing it.
The Conventional Framing
Most defenses ask a hard, unanswerable question: is this span an attack? Taint tracking asks an easier, decidable one: where did this value come from? Every value that enters the pipeline is tagged by origin — the system prompt and your own config are trusted; anything the user typed, the repo returned, or a tool emitted is untrusted. That tag is provenance, and it travels with the value.
You then propagate the taint through the pipeline and enforce one rule at the point of action: a sensitive action invoked with untrusted-tainted arguments is a policy violation. Instead of auto-committing, the call is diverted to a proposed / review lane — a human approves the write, or a stricter policy re-derives the argument from a trusted source. The dangerous write still happens; it just stops happening silently.
Why This Helps — and Where the Taint Leaks
This is the control that converts a confused-deputy write into a human-gated one. In trifecta terms, taint tracking doesn't remove any leg — the agent still has private data, untrusted content, and a channel — but it inserts a checkpoint on the exact edge that turns an injection into damage: the moment an untrusted-derived value reaches a sensitive sink. That is a genuine, decidable property, not a probability.
But we have to be honest about the hard boundary, because it is unsolved:
- Taint does not survive the LLM. The moment a tainted span and a trusted span both enter a model's context, the output tokens are a blend of the two. There is no reliable way to know which output characters were “caused by” the tainted input. Propagating taint across an LLM boundary is a research problem, not a settled one — the honest default is to treat any value that passed through a model that saw untrusted content as tainted.
- Granularity is coarse. Field-level and span-level taint is tractable; character-level and semantic taint (Agent-Sentry, semantic taint tracking) is early-stage. Too coarse and everything is tainted; too fine and the propagation logic itself becomes attackable.
- Over-tainting causes review fatigue. If every action lands in the review lane, humans rubber-stamp and the gate is theater. The value is entirely in tight scoping of what counts as a sensitive sink.
So this is a promising but partial control. It is strongest when the sensitive sinks are few and well-defined, and when the trusted path can re-derive an argument rather than trust a laundered one. It is weakest as a claim that “we track taint through the model” — that claim is not yet true.
Architecture
Components:
- Origin tagger— labels every value at ingest — system/config as trusted, user/repo/tool-output as untrusted
- Taint propagator— carries the tag through the pipeline; conservatively taints anything derived from an untrusted value
- Policy gate— at each sensitive sink, checks whether any argument is untrusted-tainted
- Review lane— the proposed/human-in-the-loop path for tainted sensitive actions — approve or re-derive, never auto-commit
Trust Boundaries
- Ingest → origin tag — the tag is only as good as the tagger; misclassifying an untrusted source as trusted defeats the whole control
- Value → derived value — propagation must be conservative — anything computed from a tainted value inherits the taint
- LLM boundary — the unsolved edge — outputs of a model that saw untrusted content are treated as tainted, because taint cannot be tracked through the tokens
- Sink → review lane — the enforced boundary — a sensitive action with tainted args cannot auto-commit; it is proposed for human approval
Threat Surface
| Threat | Vector | Impact |
|---|---|---|
| Taint laundering through the model | Untrusted content is summarized/rewritten by an LLM so the output looks trusted | Tainted value reaches a sink without a tag if the LLM boundary isn't treated as tainting |
| Origin misclassification | A source (a 'config' file an attacker can edit) is tagged trusted | Untrusted data enters with a trusted label and bypasses the gate |
| Sink under-coverage | A sensitive action the policy doesn't classify as sensitive | Tainted args auto-commit; silent poisoning of high-value state |
| Review fatigue | Over-tainting sends everything to the human lane | Approvals become reflexive; the gate stops being a real check |
The ZIVIS Position
- •Provenance is a decidable question."Where did this come from?" has an answer where "is this an attack?" does not. Build on the question you can actually answer.
- •Treat the LLM boundary as tainting.You cannot track taint through model tokens today. The honest default: any value from a model that saw untrusted content is tainted until re-validated against a trusted source.
- •Scope 'sensitive' tightly.The control lives or dies on the sink list. Gate the writes that mutate high-value state or leave the perimeter; let the rest flow. Over-tainting is a self-inflicted denial of service.
- •Re-derive, don't launder.The best resolution in the review lane isn't a human clicking approve — it's re-deriving the argument from a trusted source so it never needed trust.
- •It's a checkpoint, not a wall.Taint tracking converts a confused-deputy write into a human-gated one. Pair it with least privilege and privilege separation so a bypass still lands on limited authority.
What We Tell Clients
If your agent can mutate state that matters — merge code, move money, change access, send mail — this is where taint tracking earns its keep. Enumerate the sensitive sinks first; that list is the whole design. Tag origins at ingest, propagate conservatively, and put a policy gate in front of each sink that refuses to auto-commit when any argument carries untrusted taint.
Be clear-eyed with your team about the limit. You are not tracking taint through the model — nobody reliably is yet. What you are buying is a hard checkpoint on the edge that turns an injection into a write. That is worth a lot, and it is not everything. Keep the sink list tight so the humans in the review lane stay awake, and back it with least-privilege tooling so an approved-by-mistake write can only do so much.
Related Patterns
- Temporal Provenance— extending origin tags with when-and-in-what-order a value was seen
- Human-in-the-Loop— the review lane this pattern routes sensitive tainted actions into
- Least Privilege & Isolation— limiting what an approved-by-mistake write can reach
- Audit Logging— recording every tag, propagation, and gate decision for review
References
Frequently Asked Questions
What is taint tracking and how does it defend against prompt injection?
Taint tracking tags every value by origin, treating the system prompt and your config as trusted and anything the user typed, the repo returned, or a tool emitted as untrusted. That tag propagates through the pipeline, and at each sensitive sink a policy gate refuses to auto-commit any action invoked with untrusted-tainted arguments, diverting it to a human review lane instead. It converts a confused-deputy write into a human-gated one.
Why does taint tracking ask about origin instead of whether something is an attack?
Because where a value came from is a decidable question with a real answer, while whether a span is an attack is not. Taint tracking builds on the question you can actually answer, which gives it a genuine, decidable property rather than a probability.
Can you track taint through an LLM?
Not reliably, and this is the unsolved boundary. Once a tainted span and a trusted span both enter a model's context, the output tokens are a blend of the two, and there is no dependable way to know which characters were caused by the tainted input. The honest default is to treat any value from a model that saw untrusted content as tainted until it is re-validated against a trusted source.
What makes taint tracking succeed or fail in practice?
It lives or dies on tightly scoping what counts as a sensitive sink. If every action lands in the review lane, humans rubber-stamp and the gate becomes theater, so it is strongest when the sensitive sinks are few and well-defined and when the trusted path can re-derive an argument rather than trust a laundered one. It is a checkpoint, not a wall, and should be paired with least privilege and privilege separation so a bypass still lands on limited authority.