Redact before an external surface¶
Any free-form string that leaves the process — a telemetry event, a log
line shipped to a third-party sink, an error message attached to a crash
report — may have had a secret interpolated into it somewhere upstream.
Route it through rtb_redact::string at the boundary:
use rtb_redact::string;
fn emit(event_args: &str) {
let safe = string(event_args);
sink.send(&safe);
}
string returns Cow::Borrowed when nothing matched, so the common
clean-string case costs no allocation, and &safe derefs to &str either way.
Where exactly to put the call¶
At the last point before the string leaves your process, not at the point the string is built. One call at the edge covers every code path that feeds it, including the ones you did not write.
Good places: immediately before a telemetry or log export; when building an issue body, a webhook payload or a crash report; when writing to a sink that ships off the host.
A place it is usually wrong: a local debug log that never leaves the machine. That is where you may want the raw string, and redacting it early makes the incident harder to work.
Reuse a buffer in a hot loop¶
string_into writes into a String you own. It clears the buffer first —
it replaces the contents, it does not append to them:
use rtb_redact::string_into;
let mut buf = String::with_capacity(256);
for line in lines {
string_into(line, &mut buf);
sink.send(&buf);
}
The saving is on the clean path. When a rule does match, a fresh buffer is allocated for that rule's output anyway, so a stream of secret-bearing lines gets little from this.
Headers¶
At DEBUG/TRACE, HTTP headers get logged. string does not know about header
names, so check names against the SENSITIVE_HEADERS set and redact the values
explicitly:
use rtb_redact::{is_sensitive_header, redact_header_value};
for (name, value) in headers {
let shown = if is_sensitive_header(name) {
redact_header_value(value)
} else {
value.to_string()
};
tracing::debug!(header = name, value = %shown);
}
Redact HTTP headers covers the full recipe, including what to do about the headers the set does not list.
Tell whether a line was changed at all¶
Redaction is silent and one-way. If you need to know whether anything was
removed, match on the Cow — Owned means at least one rule fired:
use std::borrow::Cow;
use rtb_redact::string;
match string(line) {
Cow::Borrowed(s) => println!("unchanged: {s}"),
Cow::Owned(s) => println!("redacted: {s}"),
}
What not to do¶
- Don't redact typed secrets with this crate — those belong in
secrecy::SecretString, which prevents them being formatted at all.rtb-redactis the safety net for strings already assembled. - Don't build your own per-call-site regex list. The whole point is one hardened, tested, conservatively-ordered rule set; a second list drifts from this one and gets tested by nobody. The reasoning is in Why rtb-redact matches shapes, not values.
- Don't read a clean output as proof the line was safe. Plenty of shapes are not matched — see What rtb-redact does not do.
Related¶
- Redaction rules — what will and will not be caught.
- Cover a secret the rules miss.