Open source · OpenTelemetry · Apache-2.0

Let your AI agent use customer data without sending it to the LLM.

Taintward follows sensitive data through every tool call your agent makes. The model gets placeholders, third parties get nothing, your own systems get the real values back. And you get a signed report to show your customers.

breach simulator · hr_bot · fake SSN planted
INSIDE THE BOUNDARY LEAVES THE BOUNDARY
span
click a tool
what the sink received
 
decision
 
How it works

The model gets placeholders. Your agent still works.

Masking everything to [PII] breaks the agent: it can no longer write to Alice about her tax form. Taintward swaps each value for a stable placeholder on the way to the LLM, and swaps the real value back on the way to your user or your own systems.

1 · your agent sends

Write to Alice Lee (alice@acme.com)
about the missing form for
SSN 234-12-1234.

2 · the LLM provider sees

Write to <PERSON_1> (<EMAIL_1>)
about the missing form for
SSN <SSN_1>.

The real values never leave your process.

3 · your user gets

Hi Alice Lee, we are still
missing one form for the account
ending in 1234...

An external API or a log line only ever gets a one-way mask.

Install

Three ways in. Pick the one that fits.

Every path writes standard OpenTelemetry spans, so you can view them in SigNoz, Jaeger, Grafana, Datadog or anything else that speaks OTLP.

you write the agent

Python SDK

LangChain, LangGraph, the OpenAI Agents SDK, or plain OpenAI and Anthropic clients. A few lines, no decorators on every tool.

pip install "taintward[langchain]"

# unknown tools are treated as external
ward = tw.init("support-bot", destinations={
  "search_crm": "internal",
  "send_email": "external",
})
tools = tw.wrap_langchain_tools(tools)
model = tw.protect_langchain_model(model)
Integration guide
you use MCP tools

MCP gateway

Put it in front of any MCP server. No code changes. Per tool: redact, block, or only record.

# gateway.yaml
upstreams:
  crm: { command: crm-mcp-server }
tools:
  - match: "crm.get_*"
    destination: internal
  - match: "*.send_*"
    destination: external
    mode: block
Gateway guide
you run the platform

Collector + Helm

An OpenTelemetry collector with a Rego policy step. Scales out on Kubernetes, and edits to the policy roll out without a rebuild.

helm install taintward \
  ./deploy/helm/taintward \
  --set exporter.endpoint=signoz:4317

# the policy is a plain .rego file
# your security team can read
Deploy guide
Proof

When a customer asks "do you send our data to OpenAI?", send them this.

One command turns your traces into a report: what data your agents touched, where it went, what was redacted or blocked, and a signed, tamper-evident log root anyone can verify.

Agent data handling reportexample · support-bot · 30 days
12,408tool calls
831touched personal data
0raw values sent out
No raw sensitive values were sent to LLM or external destinations in this window. Method and limits are listed in section 4.
merkle root 9f2c41e0b7...a61d · signed checkpoint #212 (Ed25519)

GDPR Art. 30 and Art. 44

Which tools and which services received personal data, and which calls crossed borders.

Tamper-evident

Every violation goes into an append-only Merkle log with signed checkpoints, so a deleted or edited record is detected.

Honest by design

The report only claims what was measured, and says how. Detection accuracy is published, not promised.

The decision

No LLM judge. A policy file you can read.

The sidecar collector evaluates this Rego policy on every span before it is stored. Hover a rule to see which tools it fires on in the breach run.

collector/policy/pii_egress.regoexcerpt
package taintward # Sinks where raw sensitive data must not land.sensitive_sinks := {"external", "llm", "log"}non_adequate_jurisdictions := {"us"} # Rule 1: sensitive data reaching an external / llm / log sink.violation contains {"rule": "pii_egress", ...} if {    input.sensitive == true    input.destination in sensitive_sinks} # Rule 2: PII to a non-adequate jurisdiction (GDPR Art. 44).transfer_violation contains {"rule": "transfer", ...} if {    has_class(input.classes, "pii")    input.jurisdiction in non_adequate_jurisdictions} # Always redact raw sensitive values from stored spans.redact if {    input.sensitive == true}

rule 1 Egress

Sensitive data headed to an LLM, an external API or a log is a violation.

ask_llmcall_external_apiwrite_log

rule 2 Cross-border transfer

PII sent to a non-adequate jurisdiction, the GDPR Art. 44 case. Fires on top of rule 1.

ask_llmcall_external_api

always Redact before storage

SigNoz never stores the raw value, even for tools that legitimately need it internally.

every sensitive span
The pipeline

Six stages, all in real time.

Each stage replaces a piece of the AgentRaft research pipeline with something that runs live on OpenTelemetry.

Research to runtime

Built on research. AgentRaft measured the leak, Taintward stops it.

AgentRaft (Lin et al., 2026) found data over-exposure in 57% of the agent call chains it analyzed. Taintward keeps its formal model, D_OE = (D_trans \ (D_nec ∪ D_int)) ∩ D_total, and makes it enforceable.

AgentRaft
Taintward
propagation
LLM-judged semantic dependency
OTel baggage and span links, no LLM
necessity
Majority vote of three LLMs
Deterministic, auditable Rego policy
input
Custom benchmark trace format
Standard OTel gen_ai.* spans in SigNoz
outcome
Offline detection
Runtime enforcement, redacted before egress
evidence
Metrics
Merkle-logged lineage and blast radius
Try it

See it in one command.

Starts the collector, a demo agent that leaks an SSN and an AWS key, and a trace viewer. Open localhost:16686 and look for the raw values. You will not find them.

bash
$ git clone https://github.com/DCchoudhury15/Taintward && cd Taintward
$ docker compose up --build
# open http://localhost:16686, service taintward-hr-bot