> This page is for NeMo Relay, version v0.11.1.
> For other versions, use one of these documentation indexes:
> - Latest (v0.10.4) (default): https://docs.nvidia.com/nemo/relay/latest/llms.txt
> - dev: https://docs.nvidia.com/nemo/relay/dev/llms.txt
> - v0.11.1: https://docs.nvidia.com/nemo/relay/v0.11.1/llms.txt
> - v0.10.4: https://docs.nvidia.com/nemo/relay/v0.10.4/llms.txt
> - v0.9.4: https://docs.nvidia.com/nemo/relay/v0.9.4/llms.txt
> - v0.8.4: https://docs.nvidia.com/nemo/relay/v0.8.4/llms.txt
> - v0.7.3: https://docs.nvidia.com/nemo/relay/v0.7.3/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/nemo/relay/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/nemo/relay/_mcp/server.

# PII Redaction Behavior

> Understand PII payload boundaries, actions, selectors, trajectory preservation, and match diagnostics.

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](/nemo/relay/v0.11.1/configure-plugins/pii-redaction/configuration).
For runtime placement and backend availability, refer to
[About](/nemo/relay/v0.11.1/configure-plugins/pii-redaction/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.result` and `ToolExecutionResult.annotation` JSON
  documents. A path is relative to each document; it does not start with
  `/result` or `/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/authorization` for an exact request header field
  * `/messages/0/content` for request message content
  * `/message` for the normalized assistant response text
* Marks and non-tool, non-LLM scopes sanitize `data`, `category_profile`, and
  `metadata` as 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](/nemo/relay/v0.11.1/configure-plugins/pii-redaction/configuration#how-to-choose-a-codec)
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](/nemo/relay/v0.11.1/configure-plugins/pii-redaction/configuration#single-policy-settings)
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 `null` on 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 character
* `phone`: Preserves the last four digits while keeping separators intact
* `api_key`: Preserves the vendor-style prefix such as `sk-` and the last four characters
* `ip_address`: Preserves the last octet
* `ipv6`: Preserves the last segment
* `url`: Preserves the scheme and host, then collapses the path/query tail
* `uuid`: Preserves the last four characters
* `bearer_token`: Preserves the auth scheme and the last four characters
* `jwt`: Preserves the header segment and the tail of the signature
* `credit_card`: Preserves the last four digits while keeping separators intact
* `aws_access_key_id`: Preserves the provider prefix and the last four characters
* `aws_secret_access_key`: Preserves the last four characters
* `gcp_api_key`: Preserves the `AIza`-style prefix and the last four characters
* `azure_storage_account_key`: Preserves the last four characters

## Detector Matching

Built-in detectors are deterministic regex-backed matchers. Refer to
[Detector Presets](/nemo/relay/v0.11.1/configure-plugins/pii-redaction/configuration#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:

```toml
target_path_globs = [
  "/messages/*/content/*/text",
  "/output/*/content/*/text",
]
```

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](/nemo/relay/v0.11.1/configure-plugins/pii-redaction/configuration#targeting-and-composition)
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_paths` nor `target_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/description` or `/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 a `null` placeholder 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](/nemo/relay/v0.11.1/configure-plugins/pii-redaction/configuration#targeting-and-composition) for a complete configuration and payload comparison.

## Selection Accounting

Enable [runtime diagnostics](/nemo/relay/v0.11.1/reference/diagnostics) to distinguish a valid
selector from a policy that changed content. Refer to
[Verify the Policy](/nemo/relay/v0.11.1/configure-plugins/pii-redaction/configuration#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](#trajectory-context-preset).