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.
The three layers, in this order
Section titled “The three layers, in this order”1. Declared field: FieldRedactor
Section titled “1. Declared field: FieldRedactor”Masks the paths the connector’s manifest declares in sensitiveFields. It is pure dot-path, by
design.
2. Structural: redactStructured
Section titled “2. Structural: redactStructured”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.
3. Content: redactPii
Section titled “3. Content: redactPii”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.
What else leaves the Agent, and how it is redacted
Section titled “What else leaves the Agent, and how it is redacted”An operation’s result is not the only data that crosses the boundary. These are all the channels, and what each one goes through:
| Channel | Redaction |
|---|---|
| Operation result and error | All three layers. |
Credential probe detail, which reaches the Fleet |
The same three layers, through the same redactor, before the message is truncated. |
| Collector warnings from a capture that succeeded, and the reason a capture failed (the collector’s and aggregator’s stderr), which reach the Profiling page | The three layers, through the same redactor. Warnings are cut to 400 characters and 20 entries after redaction. |
| Learned state (topology, DNS records, load balancer routes) | The content engine, on its way out of the Agent, with one exception for IPs, described below. |
| Profiler signature | None. It carries code symbols and network peers, and what leaves is listed under Profiler. |
An infrastructure IP is not PII; an IP in free text is. In a result, the content layer masks every
IPv4, because in arbitrary text it cannot tell a customer’s address from a server’s. The learned state
can: a DNS record is configuration of your infrastructure, and the IP inside it (an A record target, a
load balancer frontend, an SPF ip4:) is the address of a resource of yours, and it is what answers
“which service does this address land on?”. So an IP passes only inside the name and target of a DNS
record, and in any other learned-state field it is masked as in a result. It is the same line the
content layer already draws for CIDR blocks. Emails, CPFs, cards and phone numbers are redacted in every
field, including inside a DNS record: a DMARC record’s rua=mailto: leaves as [EMAIL].
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”ROOTPILOT_PII_REDACTION=offThere 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 kill switch applies to every channel in the table above that goes through the content layer, including the learned state.
With the layers disabled there is no redaction to audit, so the trace is not an audit line. There are three others:
- In production, the Agent refuses to boot. With
off,RUNNER_CONNECTORS=realandNODE_ENV=production(the image’s value), boot fails naming the variable. - Otherwise, it boots with a warning in the boot log.
- And it announces the state to the control plane when it connects, and the Fleet shows the Agent as running without redaction.
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.
Where to audit
Section titled “Where to audit”| 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 |
| Kill switch | packages/core-edge/src/policy/kill-switch.ts, and the boot refusal in apps/runner/src/boot.ts |
| Learned state leaving the Agent | apps/runner/src/learned/redact.ts |
Credential probe detail |
apps/runner/src/connectors/health.ts |
| Collector warnings and failed-capture reason | apps/runner/src/profiling/push.ts |