Generate the monthly report for a customer security review¶
Goal: a customer's security team asks, every month or once a year, "do you send our customers' personal data to LLM providers or other third parties?". You send them a report for the period, and they can check that its evidence was not edited after the fact.
Time: one hour to set up, then a few minutes each month. You need: Taintward spans for the whole month (SDK or gateway, with the collector), and somewhere to run two scheduled jobs.
1. Make sure a month of spans exists¶
The report reads spans from the trace backend. The quickstart's Jaeger keeps spans in memory only, so it is not enough. Use one of:
| source | how to keep a month |
|---|---|
| SigNoz (ClickHouse) | set trace retention to at least 31 days (more if you want to regenerate old reports) |
| OTLP JSON files | turn on the collector file exporter (TAINTWARD_TRACE_EXPORTERS='[otlp_grpc/backend, file]' or Helm collector.fileExport.enabled=true) and copy the rotated files to durable storage every day |
| Jaeger with persistent storage | retention of at least 31 days |
Sampling reduces what the report counts. Do not sample Taintward spans, or say in the report what the sampling rate is.
2. Create the signing key once¶
evidence.key is created with mode 0600 and is never overwritten. Store it
in your secret manager; jobs read it from TAINTWARD_EVIDENCE_KEY_FILE (a
path) or TAINTWARD_EVIDENCE_KEY (PEM text or a 64 hex character seed).
Publish evidence.pub somewhere the customer can fetch it separately from
the report, for example your security page.
3. Record violations as they happen¶
An hourly job copies violations from the backend into the sharded evidence log and signs a checkpoint. Overlapping windows are fine: records are matched by trace id and span id, so nothing is added twice.
export TAINTWARD_EVIDENCE_DIR=/var/lib/taintward/evidence-log
export TAINTWARD_EVIDENCE_KEY_FILE=/run/secrets/evidence.key
taintward-evidence record --source clickhouse --service support-bot \
--since-mins 70 --tenant acme --checkpoint
--source jaeger(with--jaeger-url) or--source otlp-file --otlp-file DIRwork the same way. The default source ofrecordisclickhouse, which runsclickhouse-clientin the containersignoz-telemetrystore-clickhouse-0-0; use--clickhouse-url http://host:8123to use the HTTP interface instead.--tenantis one directory per customer (or per environment). Use one tenant per customer if you report per customer.- Also run
taintward-evidence checkpointonce just after midnight UTC, so each closed day is covered by a signed checkpoint.
Copy evidence-log/ to storage the writer cannot delete from (for example
S3 with Object Lock); see
Syncing checkpoints to object storage.
4. Generate the report¶
On the first working day of the month, for the month before:
taintward-report --source clickhouse --service support-bot \
--start 2026-10-01T00:00:00Z --end 2026-11-01T00:00:00Z \
--org "Example Co." --system "Support assistant" \
--title "Sensitive data flow report, October 2026" \
--evidence-dir /var/lib/taintward/evidence-log --public-key evidence.pub \
--out report-2026-10.html
taintward-report ... --format json --out report-2026-10.json # same options
- Repeat
--servicefor every service in scope (agents and the gateway). --deep-scanalso runs the Presidio detectors in the raw value check (names, locations). It is slower and gives more false findings; use it when the customer's data is mostly free text.--public-keyverifies the evidence log while building the report; the result is in section 8.
Open the HTML file and print it to PDF if the customer wants a PDF (A4, headers repeat on each page).
5. Read it before you send it¶
Check the summary statement first. It is one of four:
| statement starts with | meaning | what to do |
|---|---|---|
0 raw sensitive values were sent ... |
every external and LLM call had its input recorded and none held a raw value | send it |
N raw sensitive value(s) were found ... |
the findings table lists the calls (type and fingerprint, never the value) | investigate each call before sending; fix the tool map or allow list |
It could not be confirmed ... |
something was not measurable; the reasons are listed | fix the cause (audit mode left on, spans without input, a source that cut data short) or explain it to the customer |
No calls to external or LLM destinations were recorded |
nothing to check | check the --service names and the window |
Then check:
- section 8 (evidence log) says the log verified and every violation in the window is in the log;
- section 6 (recipients) lists only tools you expect, with the right destinations and jurisdictions; this is the "recipients" part of a GDPR Art. 30 record;
- section 9 (method and limits) matches how you run Taintward.
6. Send it¶
Send:
- the report (HTML or PDF);
- the newest checkpoint hash from section 8, in the message body, so the
customer can later detect deleted checkpoints with
--expect-head; - where to get
evidence.pub(not the key itself in the same message).
Answer the usual follow-up questions with the report's own sections:
| question | answer from |
|---|---|
| which providers receive our data? | section 6, recipients, with jurisdictions |
| was anything sent raw? | section 7, raw value check, and the summary statement |
| was anything sent to the US? | section 5, cross-border transfers |
| how do we know you did not edit the records? | section 8 and the verification steps below |
| what does this not cover? | section 9: calls that bypass Taintward, data the detectors do not recognize, passthrough_args |
7. How the customer verifies it¶
If they ask for a copy of the evidence log (or proofs for chosen violations):
taintward-evidence verify-checkpoints --evidence-dir evidence-log \
--public-key evidence.pub --expect-head <checkpoint hash from section 8>
taintward-evidence shards --evidence-dir evidence-log # compare with section 8
# one violation, without the rest of the log
taintward-evidence prove-shard --evidence-dir evidence-log --tenant acme \
--day 2026-10-14 --index 3 > proof.json # you run this
taintward-evidence verify-proof proof.json --public-key evidence.pub # they run this
verify-checkpoints must print "ok": true. The numbers can also be
recomputed: the HTML embeds its data as JSON (element taintward-report-data)
with a SHA-256 digest in the footer. The full procedure is in
How a third party verifies a report.
Checklist¶
- Trace retention of at least 31 days, no sampling of Taintward spans.
- Signing key in a secret manager, public key published.
- Hourly
record --checkpointjob and a daily checkpoint after midnight UTC. - Evidence log copied to write-once storage.
- Report generated for the full month with every in-scope
--service. - Summary statement read and explained; sections 6, 8 and 9 checked.
- Report, checkpoint hash and public key location sent.