Skip to content

PII redaction

Redaction happens at the runner’s edge, after the connector answers and before the result crosses the boundary. A single enforcement point: not scattered, not repeated on the other side.

It is active when RUNNER_CONNECTORS=real. On the synthetic path the data carries no PII, so only the declared-field layer runs.

Masks the paths the connector’s manifest declares in sensitiveFields. It is pure dot-path, by design.

An operation-keyed pass, for the shapes a regex cannot see: arbitrary property-bag values and raw end-user identifiers.

It covers exactly five Amplitude operations:

Operation Masks Redacts
analytics.searchUser userId properties
analytics.getUserActivity none properties, deviceId
replays.listSessionReplays userId none
replays.getSessionReplay userId none
replays.getSessionReplayEvents none properties

In property bags, keys are preserved and values are redacted, and the shape of the data stays legible for diagnosis, the content does not.

For every other operation, this layer is a no-op.

A final, content-aware regex pass over whatever strings are left: email, CPF, IPv4, credit card with Luhn validation, and phone numbers, plus a set of sensitive field names.

It is not dot-path driven, so it complements the two layers above and acts as defense in depth.

The trap: sensitiveFields doesn’t cross arrays

Section titled “The trap: sensitiveFields doesn’t cross arrays”

This is the trap that most often produces a false guarantee, so let’s be direct.

FieldRedactor is pure dot-path. Declaring in the manifest a field that only exists inside an array (say a field of a RumErrorSample[]) redacts nothing. And that is worse than not declaring it, because it produces the appearance of a guarantee that isn’t there.

For that case, the rule belongs in redactStructured, which is operation-keyed and does cross arrays.

The kill switch, and what it doesn’t disable

Section titled “The kill switch, and what it doesn’t disable”
Terminal window
ROOTPILOT_PII_REDACTION=off

There is a legacy alias, INFRAINTEL_PII_REDACTION. When both are present, the new name wins.

Be precise about its reach:

Layer With ROOTPILOT_PII_REDACTION=off
1. Declared field still runs
2. Structural disabled
3. Content disabled

The disabled layers audit the decision to stderr, so turning it off is not silent.

Divergent classification: the edge exposes, it doesn’t rewrite

Section titled “Divergent classification: the edge exposes, it doesn’t rewrite”

One case that shows up in practice and is worth understanding before filing it as a bug.

Datadog sends all stderr to status:error. So a search for errors can return [INFO] Worker exiting. The source classified it as an error, the text says otherwise.

The edge exposes the divergence rather than rewriting the field: alongside the source’s level, it emits textLevel and levelMismatch. The principle is that the source’s classification is a fact about the source, and silently correcting it would erase information.

What Where
The three-layer composition apps/runner/src/redact/pii-redactor.ts
Declared-field layer apps/runner/src/redact/field-redactor.ts
Structural layer, per operation packages/core-edge/src/policy/structured-redactor.ts
Content engine packages/core-edge/src/policy/pii-redactor.ts