Skip to main content
AgentMark PII masking strips sensitive data from span attributes before traces leave your application. Masking runs in your application process, so it redacts configured attributes before the OTel exporter sends them over the network. You can use a custom mask function, the built-in PII masker, environment variable suppression, or any combination of these approaches. Coverage depends on your configuration: masking covers only the attributes you target.

How it works

AgentMark implements PII masking as an OpenTelemetry SpanProcessor that wraps the export pipeline. When a span finishes, the masking processor intercepts it before the exporter sends data over the network.
1

Span finishes

Your application completes an LLM call or tool invocation. The span is ready for export.
2

MaskingSpanProcessor intercepts

If you configure masking, the processor runs env var suppression first, then your mask function on each sensitive attribute.
3

Redacted span exported (or dropped)

If masking succeeds, the processor forwards the redacted span to the exporter. If the mask function throws, the processor drops the span entirely (fail-closed) and logs a warning.
This means:
  • The processor redacts configured attributes before export. The processor runs in-memory, in your application, before any network call, so attributes you mask never leave the process in their raw form.
  • Zero overhead when masking is off. The SDK only adds the processor to the pipeline when you configure a mask function or set env vars.
  • Standard OTel pattern. The MaskingSpanProcessor wraps your existing BatchSpanProcessor or SimpleSpanProcessor, with no forking or patching required.

Before and after

With createPiiMasker() enabled, PII tokens like [EMAIL], [SSN], and [PHONE] replace sensitive data in the trace viewer:
Trace with PII masking enabled, sensitive data replaced with tokens like [EMAIL], [SSN], [PHONE]
With AGENTMARK_HIDE_INPUTS=true, all input attributes show [REDACTED] while outputs remain visible:
Trace with input suppression, all inputs show [REDACTED]
Here’s what the raw span attributes look like with each approach: Without masking:
With createPiiMasker({ email: true, ssn: true }):
With AGENTMARK_HIDE_INPUTS=true:
Notice that gen_ai.request.model and gen_ai.usage.total_tokens are never masked. These operational attributes contain no user data.

Basic usage

Pass a mask function to AgentMarkSDK. The function receives each string attribute value and returns the redacted version.
AgentMark calls the mask function on every maskable span attribute before it hands the span to the exporter. The function must be synchronous. You have full control over the replacement logic.

Built-in PII masker

AgentMark ships a built-in PII masker that covers common patterns. Enable the patterns you need:

Built-in patterns

  • email: Matches email addresses like user@example.com. Replaced with [EMAIL].
  • phone: Matches phone numbers like (555) 123-4567. Replaced with [PHONE].
  • ssn: Matches Social Security numbers like 123-45-6789. Replaced with [SSN].
  • creditCard / credit_card: Matches credit card numbers like 4111 1111 1111 1111. Replaced with [CREDIT_CARD].
  • ipAddress / ip_address: Matches IP addresses like 192.168.1.100. Replaced with [IP_ADDRESS].
All patterns default to false. AgentMark applies only the patterns you explicitly enable.
TypeScript uses camelCase (creditCard, ipAddress) while Python uses snake_case (credit_card, ip_address) following each language’s conventions.

Custom patterns

You can add custom patterns alongside the built-in ones. Each entry needs a regex pattern and a replacement string.
Custom patterns run after built-in patterns. You can use custom patterns on their own without enabling any built-in patterns.

Environment variable suppression

For a zero-code option, set environment variables to suppress all inputs, all outputs, or both:
When enabled, these replace ALL input or output attribute values with [REDACTED]. This requires no code changes.
If you configure both environment variable suppression and a mask function, suppression runs first. The mask function then receives the already-suppressed values.

Masked attributes reference

AgentMark masks specific span attributes depending on their category. Input attributes (suppressed by AGENTMARK_HIDE_INPUTS):
  • gen_ai.request.input: the prompt or messages sent to the model
  • agentmark.request.input: vendor-namespaced input written by observe() / setInput() (dual-emitted with gen_ai.request.input)
  • gen_ai.request.tool_calls: tool call arguments included in the request
  • agentmark.dataset_input: the dataset row input attached during experiment runs
  • ai.prompt, ai.prompt.messages, ai.prompt.tools, ai.prompt.toolChoice, ai.toolCall.args: prompt, message, and tool-call content emitted by the Vercel AI SDK (experimental_telemetry)
  • gen_ai.input.messages, gen_ai.system_instructions, gen_ai.tool.definitions, gen_ai.tool.call.arguments: input content per the OpenTelemetry GenAI semantic conventions
  • gen_ai.prompt: legacy OpenTelemetry GenAI prompt attribute
  • gen_ai.tool.input: tool input recorded by the Claude Agent SDK integration
Output attributes (suppressed by AGENTMARK_HIDE_OUTPUTS):
  • gen_ai.response.output: the model’s text response
  • agentmark.response.output: vendor-namespaced output written by observe() / setOutput() (dual-emitted with gen_ai.response.output)
  • gen_ai.response.output_object: structured output from the model
  • ai.response.text, ai.response.toolCalls, ai.response.object, ai.result.text, ai.result.toolCalls, ai.result.object, ai.toolCall.result: response and tool-result content emitted by the Vercel AI SDK (experimental_telemetry)
  • gen_ai.output.messages, gen_ai.tool.call.result: output content per the OpenTelemetry GenAI semantic conventions
  • gen_ai.completion: legacy OpenTelemetry GenAI completion attribute
  • gen_ai.tool.output: tool output recorded by the Claude Agent SDK integration
Metadata attributes (mask function only, not affected by env vars):
  • agentmark.metadata.*: custom metadata attached to spans
Operational attributes such as trace IDs, model names, and token counts are never masked. These contain no user data, and observability needs them to function.

Error handling

PII masking uses fail-closed behavior. If your mask function throws an error, the processor drops the span entirely and never exports it. This ensures that unmasked data never reaches the trace backend. Tracing continues normally for subsequent spans after a mask failure. The dropped span doesn’t affect the rest of the trace pipeline.
Test your mask function thoroughly before deploying to production. A mask function that throws on unexpected input causes spans to be silently dropped.

Recipes

Microsoft Presidio (Python)

Microsoft Presidio uses NLP to detect unstructured PII like person names, addresses, and passport numbers that regex patterns miss. Since Presidio is a Python library, this recipe applies to the Python SDK.
Python
Presidio detects 15+ entity types including PERSON, LOCATION, US_PASSPORT, IBAN_CODE, and CRYPTO. See the Presidio supported entities for the full list.
For TypeScript applications, use createPiiMasker() with custom regex patterns for common PII types. Presidio requires a Python runtime and NLP models (~500MB) which makes it better suited for Python services.

Healthcare (HIPAA)

Combine built-in patterns with custom patterns for Protected Health Information:
Python

Financial services

For PCI-DSS compliance, enable credit card masking and add patterns for financial identifiers:
TypeScript
For maximum compliance assurance, combine a mask function with AGENTMARK_HIDE_INPUTS=true as a defense-in-depth strategy. The env var acts as a safety net in case a new input attribute appears that the mask function doesn’t cover.

Next steps

Verify your masking in the trace detail view: see Traces and logs for where span attributes appear.

Have questions?

Reach out any time: