PII Redaction Behavior
Use this page to understand which values a policy selects and what Relay publishes after sanitization. For configuration keys and complete before/after examples, refer to Configuration. For runtime placement and backend availability, refer to About.
Observability Semantics
Payload boundaries and event publication
The plugin uses different payload boundaries for tools and LLMs:
- Tools use JSON-native payloads. Paths point into the emitted tool args
directly. For built-in action policies, including detector-backed policies,
tool response paths apply independently to the emitted
ToolExecutionResult.resultandToolExecutionResult.annotationJSON documents. A path is relative to each document; it does not start with/resultor/annotation. - LLM requests use the active resolved codec for that managed call. LLM
responses use the active built-in codec for normalized projection. Prefer
normalized Relay paths such as:
/headers/authorizationfor an exact request header field/messages/0/contentfor request message content/messagefor the normalized assistant response text
- Marks and non-tool, non-LLM scopes sanitize
data,category_profile, andmetadataas separate JSON values. A target path is evaluated independently inside each value; it does not start with/data,/category_profile, or/metadata.
When using LLM selectors, supply the matching codec on the call, or configure a
codec fallback
for calls without an active codec. If a required codec is unavailable or Relay
cannot safely sanitize and convert the payload, it omits the LLM payload and
its annotation from the recorded event.
The PII policy changes observability data only. It leaves inputs sent to providers and tools, and results returned to your application, unchanged. Subscribers and exporters receive sanitized events; values removed by the policy are unavailable to them.
Metadata is sanitized separately from request and result payloads. In the single-policy configuration, use the surface flags to choose which event surfaces are sanitized.
Action Semantics
How each action changes a selected value
remove
remove is structural.
Configure at least one target_paths or target_path_globs selector when
using remove. Relay rejects an unscoped remove policy during validation to
prevent accidental removal of every string value in the selected surfaces.
When a target matches:
- Object fields are removed.
- Array elements become
nullon JSON-native surfaces. If a normalized LLM array removal cannot be safely re-encoded, Relay omits the event payload and its annotation instead. - Targeted scalar or root values become
null.
regex_replace
regex_replace applies the configured regex to matching string leaves and
replaces matches with the configured replacement.
If you set detector instead of pattern, the built-in backend uses the
detector’s stock matcher regex.
redact
redact is the deterministic whole-match replacement lane.
It uses the same pattern or detector matcher flow as regex_replace, but
defaults the replacement token to [REDACTED] and is intended for cases where
you do not want to preserve any matched secret characters.
Use redact when you want:
- credential-style secrets fully replaced
- a consistent redaction token across detectors
- clearer policy intent than a custom
regex_replace
hash (SHA-256)
hash uses SHA-256 on the UTF-8 bytes of the selected text, without a salt.
It returns the digest as 64 lowercase hexadecimal characters.
Without pattern or detector, the entire selected string is hashed.
With either matcher, each matching substring gets its own SHA-256 digest;
unmatched text is preserved.
mask
mask replaces the middle portion of each matching string leaf with the
configured mask_char.
Use unmasked_prefix and unmasked_suffix when you want to preserve a small
leading or trailing segment for correlation or debugging, such as the last four
characters of a token.
When pattern or detector is set, mask only masks matching substrings
inside the string leaf.
When detector is set and you do not specify unmasked_prefix or
unmasked_suffix, the built-in backend applies detector-aware defaults:
email: Preserves the domain and the first local-part characterphone: Preserves the last four digits while keeping separators intactapi_key: Preserves the vendor-style prefix such assk-and the last four charactersip_address: Preserves the last octetipv6: Preserves the last segmenturl: Preserves the scheme and host, then collapses the path/query tailuuid: Preserves the last four charactersbearer_token: Preserves the auth scheme and the last four charactersjwt: Preserves the header segment and the tail of the signaturecredit_card: Preserves the last four digits while keeping separators intactaws_access_key_id: Preserves the provider prefix and the last four charactersaws_secret_access_key: Preserves the last four charactersgcp_api_key: Preserves theAIza-style prefix and the last four charactersazure_storage_account_key: Preserves the last four characters
Detector Matching
Built-in detectors are deterministic regex-backed matchers. Refer to Detector Presets for the supported detector names.
bearer_token is heuristic rather than vendor-specific. It can still match
benign bearer-style values, so prefer a narrower detector when you know the
credential family.
For actions other than remove, if both target-selector lists are empty, the
built-in backend sanitizes every matching string leaf in the selected payload
boundary. The remove action requires at least one selector.
Path Semantics
Exact pointers, globs, and container selection
target_paths are exact RFC 6901 JSON-pointer matches. target_path_globs
adds a restricted, opt-in selector language: a whole * segment matches one
object key or array index. Exact paths and glob paths are combined.
Within target_path_globs, * is a wildcard only when it is the complete
pointer segment. Segments such as content* and foo*bar match those literal
object keys.
For example, these selectors cover every message and provider-output text block without capping the number of array items:
For example, /messages/*/content matches both
/messages/A/content and /messages/B/content. If either content value is
an array or object, redact does not redact its descendant strings: it acts
only on selected string values. For content parts such as
{"messages":[{"content":[{"text":"alice@example.com"}]}]}, use
/messages/*/content/*/text to select the text string. The same glob also
matches a text key in an object one level below content.
Globs are not recursive: /output/* selects only a direct child of output.
It does not select /output/0/content/0/text; use
/output/*/content/*/text for that shape. Relay does not support **,
predicates, or arbitrary JSONPath expressions. target_paths = ["/*"]
continues to select a literal * object key; use target_path_globs = ["/*"]
when * should be a wildcard.
Relay validates every configured path as RFC 6901 syntax before activating the
component. The empty string selects the document root. Every other pointer must
start with /, and ~ escapes must use ~0 for ~ or ~1 for /. Invalid
pointers prevent activation instead of silently leaving the intended values
unsanitized.
Trajectory Context Preset
What the trajectory preset removes and preserves
trajectory_context is a fail-closed policy for metrics-oriented trajectory
export. It discards original LLM request and response payloads and generates
minimal provider-shaped projections from sanitized normalized annotations.
For supported built-in codecs, the published LLM request or response retains
supported structure and analytics, including message roles, tool names, and
tool-call correlation IDs. Conversational text is replaced with [REDACTED] by
default. Tool arguments and parameter schemas are emptied. Refer to the
Trajectory removal worked example
for the request, response, and separate tool events before and after sanitization.
A configured legacy codec can also provide this projection when no active codec
is present. Custom runtime and opaque codec identities publish {} instead,
even when their payload resembles a supported provider’s JSON shape.
Unknown fields and unstructured payloads are discarded or emptied. This includes
provider extensions, additional choices or candidates, headers, provider-native
values, generic event data, and category-profile extras. Present opaque typed
JSON values become {}. Metadata retains only explicitly supported fields,
including recognized scope status values and valid nemo_relay.log.severity
values for marks. Unknown custom-mark payloads are emptied by default;
custom_mark_payload_policy = "preserve" explicitly retains them.
Scope metadata retains only recognized otel.status_code values (OK, ERROR,
UNSET) and, for ERROR, Relay’s fixed error.type classifications or _OTHER.
Unrecognized error types are discarded; the full and gen_ai OpenTelemetry
projections use _OTHER for errors without a classification. Free-form status
descriptions, exception types, and other metadata remain discarded, so redaction
preserves success/error reporting without exporting error messages or application
exception names.
Typed LLM annotations retain message roles and content-part kinds; provider,
model, and tool names; request tuning settings; finish reasons; token and cache
usage; and normalized cost amounts, currency, and source classification.
Conversational content and other application identifiers use the configured
replacement, which defaults to [REDACTED]. Opaque typed fields become {};
approved typed numeric and boolean analytics remain available. Routing metadata,
pricing provenance, optimization contribution evidence, and unknown fields are
discarded. Relay lifecycle identifiers remain unchanged.
Tool-call correlation IDs remain unchanged across LLM messages and tool lifecycle events so exporters can associate results with the correct calls. Response IDs and other application identifiers still use the replacement marker.
Provider, model, tool, and metric names are intentional free-form exceptions.
Do not place user content, secrets, or personal identifiers in these fields.
All redacted application identifiers collapse to the same marker and are not
suitable correlation keys. To retain an unknown custom mark unchanged, set
custom_mark_payload_policy = "preserve" explicitly and only for a trusted
producer.
Typed Metric Marks
Typed metric sanitization and allowed dimensions
Relay recognizes marks that use its nemo.relay.metric_measurements schema as
typed metric envelopes. PII sanitization preserves the measurement name, kind,
value type, numeric value, and histogram boundaries so the OpenTelemetry metric
exporter can validate and export the mark.
The plugin sanitizes optional text without invalidating the envelope:
- With neither
target_pathsnortarget_path_globs, measurement descriptions and string-valued metric attributes follow the configured built-in action. - With target selectors, those fields are sanitized only when their
payload-relative JSON Pointer matches, such as
/measurements/0/descriptionor/measurements/0/attributes/owner. - With
action = "remove", targeting a whole string-array attribute removes that attribute. Targeting an individual element removes that element and compacts the remaining array; Relay cannot use anullplaceholder because typed OpenTelemetry metric attributes must remain valid homogeneous arrays. - Outside
trajectory_context, numeric and boolean metric attributes remain analytical telemetry.
The trajectory_context preset replaces descriptions with its configured
replacement while retaining required measurement fields. It drops every
metric attribute except an exact string value admitted by
metric_string_attribute_allowlist. That preset does not accept target
selectors. Its custom_mark_payload_policy applies only to opaque marks in the
custom category; it does not control Relay typed metric envelopes.
Matching is case-sensitive and requires both the attribute name and value. A string-array attribute is retained only when every element is allowed; one unexpected value drops the whole attribute. Values under an unlisted key, unexpected values under a listed key, and numeric or boolean attributes are dropped. This allowlist does not apply to descriptions, mark metadata, category profiles, or non-metric marks. Configure fixed constants or bounded enums only; do not list free-form identifiers or user-provided text.
On every mark, the preset preserves the reserved nemo_relay.log.severity
metadata field in canonical form when it contains a supported severity. For
opaque custom marks using redact_all_leaves, unsupported severity values and
all other opaque fields are discarded.
Refer to the Metric dimensions example in Targeting and Composition for a complete configuration and payload comparison.
Selection Accounting
Enable runtime diagnostics to distinguish a valid selector from a policy that changed content. Refer to Verify the Policy for the diagnostic fields and setup.
With action = "remove", a child string inside a selected container can count
as selected even though removing the container prevents the sanitizer from
visiting the child. Only the removed container counts as affected. A selected
string can also remain unchanged when its detector finds nothing.
With neither selector list configured, matched_paths counts all visited JSON
nodes and eligible_string_leaves counts the eligible string nodes.
The trajectory_context preset uses its semantic sanitization path rather than
this selector diagnostic. Use the preset when the goal is broad payload removal;
refer to Trajectory Context Preset.