Where redaction applies¶
Two of an event's fields are scrubbed for secrets automatically. One is not. The split is deliberate, and getting it wrong is the most likely way to leak something.
The rule¶
Event::redacted() returns a clone with args and err_msg passed
through rtb_redact::string.
FileSink, HttpSink and OtlpSink — every sink that writes outside the
process — call it before serialising. MemorySink and NoopSink do not,
because neither one lets the event leave.
Nothing else in the event is touched: not name, not tool, not
machine_id, and not attrs.
Why args and err_msg are scrubbed and attrs are not¶
args and err_msg exist to hold text nobody controls the shape of. A
command line contains paths, hostnames and the occasional pasted token; an
error message contains whatever an upstream library decided to print,
including the Authorization header it just failed to authenticate with.
Fields whose whole purpose is arbitrary text get a scrubber by default,
because the alternative is asking every caller to remember.
attrs is the opposite: a map the caller fills in with values the caller
chose. The intended contents are a command name, an outcome such as
ok/error/cancelled, a duration bucket, a version string — a small
enumerated vocabulary that is safe by construction and useful precisely
because it is stable. Running a secret-scrubber over a set of known-safe
enumerated values buys nothing and costs a regex pass per attribute.
So the boundary is: free-form text goes in args or err_msg and gets
scrubbed; enumerated values go in attrs and do not. If you have
free-form text and want it redacted, either put it in one of the two
scrubbed fields or call rtb_redact::string yourself before inserting it.
What the scrubber catches¶
rtb-redact applies a fixed set of patterns and replaces each match with
[redacted]:
- URL userinfo —
postgres://app:hunter2@db/mydb Bearer/Basic/Tokencredentials in header-shaped text- sensitive query parameters —
api_key,access_token,token,password,secret,signature,authand near-spellings of those - well-known provider key prefixes —
sk-,sk-ant-,ghp_,glpat-,AIza,AKIA/ASIA,xoxb-and friends, at 20 characters or more - JWT-shaped tokens of 100 characters or more
- PEM private key blocks
- any whitespace-bounded run of 40+ base64/hex-ish characters
What it does not catch, and why that matters here¶
The list above is a pattern set, not a proof. A credential that does not match a pattern — a short shared password, an internal token with a bespoke shape, a customer name in a file path — passes through unchanged. Redaction reduces the blast radius of an accidental paste; it is not a licence to send arbitrary text to a collector.
Which is the real reason attrs is the safe-by-construction field. The
enumerated-value discipline is what keeps sensitive data out of events in
the first place, and the scrubber is the safety net under the two fields
where that discipline cannot apply.
Redaction happens per emit, not once¶
redacted() clones the event and scrubs the clone, so the event you hold
after record is unchanged, and each sink scrubs independently. With a
MemorySink in a test you are therefore asserting against the raw event —
useful when you want to check what the caller passed, and worth remembering
when you are trying to prove that redaction happened. To test redaction,
assert on the FileSink output, or call event.redacted() yourself.