Skip to content

API reference

The crate exports five public items and nothing else. This page states what each one does, what it returns for awkward input, and what it allocates. The rules those functions apply are catalogued separately in Redaction rules.

Everything here is drawn from src/lib.rs at version 0.6.3.

Crate facts

Crate name rtb-redact (import path rtb_redact)
Minimum supported Rust version 1.82
Edition 2021
Runtime dependencies regex, phf — two, not zero
unsafe #![forbid(unsafe_code)] in the crate root
Replacement marker the literal string [redacted]
Feature flags none — there is nothing to switch on or off
Configuration none — the rule set is fixed at compile time

There is no builder, no options struct, and no way to add, remove or reorder a rule from outside the crate. If you need a pattern the crate does not carry, see Cover a secret the rules miss.

string — redact a &str

pub fn string(input: &str) -> Cow<'_, str>

Applies every rule in the catalogue to input and returns the result. Marked #[must_use]; ignoring the return value redacts nothing, because input is not modified.

Returns Cow::Borrowed — no allocation — when the input came through untouched. That covers three cases: an empty input, an input that contains none of the anchor characters, and an input where the anchor check passed but no rule actually changed anything.

Returns Cow::Owned when at least one rule replaced something.

use rtb_redact::string;

let out = string("connect to postgres://app:hunter2@db/mydb");
assert_eq!(out, "connect to postgres://[redacted]@db/mydb");

Allocation: one String for the initial copy, plus one more for each rule that matches. A clean string allocates nothing at all.

string never panics. All seven patterns are compiled once from string literals in a LazyLock, so there is no user-supplied pattern to fail at runtime and no lock contention after first use. Calling it from several threads is safe.

string_into — redact into a buffer you own

pub fn string_into(input: &str, out: &mut String)

Writes the redacted form of input into out. It clears out first, so this replaces the buffer's contents — it does not append to them:

use rtb_redact::string_into;

let mut buf = String::from("previous line");
string_into("Authorization: Bearer sk-ant-api03-abcdefghijklmnop", &mut buf);
assert_eq!(buf, "Authorization: Bearer [redacted]");

An empty input leaves out empty.

The saving over string is real only on the clean path, where the input is copied straight into the buffer you already allocated. As soon as a rule matches, the implementation builds a fresh String for that rule's output and moves it into out, so a redacting call still allocates — once per matching rule. Reach for string_into when most of your traffic is clean and you are calling it in a tight loop; otherwise string is simpler and no slower.

SENSITIVE_HEADERS — the header-name set

pub static SENSITIVE_HEADERS: phf::Set<&'static str>

The ten HTTP header names whose values should never be logged. It is a compile-time perfect-hash set, so membership is O(1) and costs no startup work.

Header name
authorization
proxy-authorization
cookie
set-cookie
x-api-key
x-auth-token
x-amz-security-token
x-goog-api-key
x-anthropic-api-key
x-openai-api-key

Every entry is stored lowercase and the set itself is case-sensitive. Match against it through is_sensitive_header, which does the lowercasing for you; a direct SENSITIVE_HEADERS.contains("Authorization") returns false.

The set is fixed. There is no registration hook, so a header your service treats as a credential — x-gitlab-token, x-hub-signature, a bespoke internal one — is not in it and will not be reported as sensitive.

is_sensitive_header — test a header name

pub fn is_sensitive_header(name: &str) -> bool

ASCII-case-insensitive membership test against SENSITIVE_HEADERS. Marked #[must_use].

use rtb_redact::is_sensitive_header;

assert!(is_sensitive_header("Authorization"));
assert!(is_sensitive_header("X-API-Key"));
assert!(!is_sensitive_header("content-type"));

Two behaviours worth knowing:

  • Names longer than 128 bytes return false without being looked up. The lowercasing buffer is a fixed 128-byte array, and no name in the set is anywhere near that long, so an oversized name cannot be a member anyway.
  • Only ASCII letters are folded. A header name is ASCII in practice; a non-ASCII byte is passed through unchanged and simply will not match.

redact_header_value — mask a header value

pub fn redact_header_value(value: &str) -> String

Returns [redacted] for any non-empty value, and an empty String for an empty one. Marked #[must_use].

use rtb_redact::redact_header_value;

assert_eq!(redact_header_value("Bearer abc123"), "[redacted]");
assert_eq!(redact_header_value(""), "");

It does not inspect the value, and it does not check the header name — it masks unconditionally. Deciding whether to call it is the caller's job, which is what is_sensitive_header is for. A whitespace-only value counts as non-empty and becomes [redacted].

Note the asymmetry with string: redact_header_value allocates a new String for every non-empty value, even one that would have survived string untouched. The empty case returns String::new(), which allocates nothing.

Which function do I want?

Situation Use
A free-form line — log message, error text, telemetry attribute string
The same, in a hot loop with a buffer to reuse string_into
An HTTP header you are about to log is_sensitive_header, then redact_header_value
Deciding whether your own header list is complete read SENSITIVE_HEADERS, then add your own names on top

What the API deliberately does not offer

  • No Error helper. Unlike the Go sibling redact.Error, there is no nil-safe error wrapper here; call string(&err.to_string()) yourself.
  • No &[u8] entry point. Input is &str, so callers holding bytes convert first — and a byte slice that is not valid UTF-8 has no redacted form to return.
  • No custom patterns, no configuration, no runtime registration. See What rtb-redact does not do for the reasoning.