Skip to content

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:

# Cargo.toml
[dependencies]
rtb-redact = "0.6"
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 CowOwned 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-redact is 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.