rtb-redact 0.6.5

Free-form secret redaction for log lines, telemetry events, and diagnostic surfaces. Part of the phpboyscout Rust toolkit.
Documentation
---
title: API reference
description: Every public item in rtb-redact, with its signature, return value, defaults, and behaviour on edge-case input.
---

# 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](rules.md).

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.85 |
| 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](../how-to/cover-a-secret-the-rules-miss.md).

## `string`: redact a `&str`

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

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

**Returns `Cow::Borrowed`**, with no allocation, when the input came through
untouched. That covers three cases: an empty input, an input that contains none
of the [anchor characters](rules.md#the-fast-path-and-what-it-skips), and an
input where the anchor check passed but no rule actually changed anything.

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

```rust
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

```rust
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 rather than appending to them:

```rust
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

```rust
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`](#is_sensitive_header-test-a-header-name),
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

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

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

```rust
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

```rust
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]`.

```rust
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]../explanation/what-rtb-redact-does-not-do.md
  for the reasoning.