Span attributes
Every attribute Taintward writes, by who writes it. Names follow the
OpenTelemetry GenAI semantic conventions (gen_ai.*) where one exists, and
use the taintward.* prefix otherwise. The names are part of the public API
(see Stability and versioning ): dashboards,
alerts, the collector policy and the report depend on them.
Values are never raw sensitive data. Inputs and outputs are stored redacted
or masked.
Resource
attribute
value
service.name
the name given to tw.init() / configure_tracing(), or the gateway's service_name
service.namespace
taintward (set by configure_tracing)
Taint (SDK and gateway)
attribute
type
meaning
taintward.sensitive
bool
the call, or the run or session it belongs to, touched sensitive data
taintward.taint.classes
string
comma list of classes, sorted: pii, secret
taintward.taint.level
string
low, medium (PII) or high (a secret)
taintward.taint.id
string
random id of the taint label, the same across the run
taintward.taint.source_span
string
span id (16 hex chars) of the call that first found the data (SDK)
The same label travels in OTel baggage as taint.id, taint.classes,
taint.level and taint.source. Baggage from outside is not trusted for
anything but lineage.
Call (SDK and gateway)
attribute
type
written by
meaning
taintward.destination
string
both
internal, external, llm, log or rag
taintward.jurisdiction
string
both
from the tool map, or llm_jurisdiction for LLM spans; empty when not set
taintward.violation
bool
both
a tainted run or session reached an external, llm or log sink
taintward.violation.reason
string
both
short reason, for example tainted chain reached llm sink
taintward.redacted
bool
SDK
the gate changed what the sink received
taintward.args_redacted
bool
both
the stored gen_ai.tool.call.arguments are already redacted for the destination; the collector keeps them as they are (so internal tokens survive)
taintward.dry_run
bool
both
audit mode: the sink received raw input (SDK TAINTWARD_DRY_RUN, gateway mode: audit)
taintward.tool.error
string or bool
both
the sink raised (SDK: the message with sensitive data masked) or returned an MCP error (gateway: true)
taintward.framework
string
SDK adapters
langchain, openai-agents, openai.chat, openai.responses, anthropic
Gateway only
Span name: taintward.gateway <tool>.
attribute
type
meaning
taintward.mode
string
redact, block or audit
taintward.blocked
bool
the call was refused and not forwarded
taintward.policy.rules
string
rules that fired, for example pii_egress,transfer
taintward.args.redacted
bool
the forwarded arguments differ from what the agent sent
taintward.result.redacted
bool
the result was masked before the agent saw it
taintward.gateway.upstream
string
upstream name from the config
mcp.method.name
string
tools/call
Collector (taint_policy processor)
The collector removes any of the attributes it owns that a sender set
itself, then writes them from its own decision.
attribute
type
meaning
taintward.policy.violation
bool
the pii_egress rule fired
taintward.policy.transfer_violation
bool
the transfer rule fired
taintward.policy.violations
string
the violations as JSON, for example [{"rule":"pii_egress","sink":"llm"}]
taintward.policy.reasons
string
comma list of rule names
taintward.policy.redacted
bool
the policy asked for redaction (every sensitive span), or the policy failed
taintward.policy.decision_engine
string
rego
taintward.policy.masked_values
int
how many values the collector masked on this span (only when more than 0)
taintward.policy.untrusted_producer
bool
the span's service is not in trusted_services; it was treated as sensitive and its taintward.args_redacted marker was ignored
taintward.policy.dropped_attributes
int
how many attributes the collector removed with drop_attributes (only when more than 0)
taintward.policy.error
string
the policy could not be evaluated; the span was masked anyway
GenAI semantic conventions
attribute
written on
meaning
gen_ai.operation.name
all
execute_tool for tools, chat for model calls
gen_ai.tool.name
tool spans
the tool name (the exposed name in the gateway)
gen_ai.tool.call.arguments
tool spans
input after redaction (masked in audit mode)
gen_ai.tool.call.result
tool spans
output, masked
gen_ai.input.messages
model spans
request after redaction (placeholders or masks)
gen_ai.output.messages
model spans
response as the provider sent it (placeholders, not restored), masked
gen_ai.provider.name
model spans
openai, anthropic, ...
gen_ai.request.model
model spans
the requested model
gen_ai.usage.input_tokens, gen_ai.usage.output_tokens
model spans
token usage when the provider reports it
taintward.placeholders.instruction
model spans
true when the placeholder system line was added
taintward.placeholders.repaired
model spans
placeholders the model wrote in another form ([PERSON_1], Person 1) that were still restored
taintward.placeholders.lost
model spans
placeholders the model broke so they could not be restored
taintward.placeholders.unknown
model spans
placeholder-like text that matches nothing in the run
taintward.placeholders.blocked
model spans
known placeholders the model wrote inside a link or image address; left as placeholders on purpose. Often a sign of prompt injection
Span events
event
where
attributes
taintward.reverse
the span that is current when Redactor.reverse or tw.restore runs, or a taintward.reverse span of its own when none is
taintward.reverse.kind (token or placeholder), taintward.reverse.found (how many values were found), taintward.reverse.caller (module:function:line of the calling code), taintward.key_id (tokens only). Never the value or the token. The same line goes to the taintward.audit logger
Useful filters
question
filter
all leaks the collector confirmed
taintward.policy.violation = true
cross-border transfers
taintward.policy.transfer_violation = true
calls the gateway refused
taintward.blocked = true
anything still in audit mode
taintward.dry_run = true
model calls that carried personal data
gen_ai.operation.name = chat AND taintward.sensitive = true
policy failures
taintward.policy.error EXISTS