---
title: rtb-redact
description: Free-form secret redaction for logs, telemetry events, and diagnostic surfaces.
---
# `rtb-redact`
Strips secrets out of free-form strings before they reach telemetry,
distributed logs, or any third-party observability surface. The rule set
runs in a fixed order and is deliberately conservative: it errs toward
over-redaction.
It works by **shape**, not by value: it holds no list of your secrets and
recognises credentials by what they look like, so it catches the token an
upstream error quoted back at you as readily as the one you were holding.
Part of the [phpboyscout Rust toolkit](https://rust.phpboyscout.uk);
extracted from
[rust-tool-base](https://gitlab.com/phpboyscout/rust-tool-base).
```toml
[dependencies]
rtb-redact = "0.6"
```
Minimum supported Rust version 1.85. Two runtime dependencies, `regex` and
`phf`. No `unsafe`: the crate root carries `#![forbid(unsafe_code)]`.
## Public API
```rust
use rtb_redact::string;
let scrubbed = string("connect to postgres://app:hunter2@db/mydb");
assert!(scrubbed.contains("[redacted]"));
assert!(!scrubbed.contains("hunter2"));
```
| `string` | Redact a `&str`, returning `Cow`: `Borrowed` when nothing matched (no allocation on the common case), `Owned` otherwise. |
| `string_into` | Same rules, writing into a caller-supplied `String` so a buffer can be reused. Clears the buffer first, so it replaces the contents, it does not append. |
| `SENSITIVE_HEADERS` | `phf::Set` of the ten header names whose values must be redacted at DEBUG/TRACE. O(1) lookup. |
| `is_sensitive_header` | ASCII-case-insensitive membership test against `SENSITIVE_HEADERS`. |
| `redact_header_value` | Mask a single header value: `[redacted]` for anything non-empty, `""` for empty. |
Every signature, default and edge case is in the [API
reference](reference/api.md). The rustdoc is on
[docs.rs/rtb-redact](https://docs.rs/rtb-redact).
There is nothing to configure: no feature flags, no options struct, no way to
add a pattern at runtime.
## What gets redacted
`string` strips URL userinfo, common credential query parameters,
`Bearer`/`Basic`/`Token` credentials, PEM private-key blocks, well-known
provider prefixes (`sk-`, `sk-ant-`, `ghp_`, `glpat-`, `AIza`, `AKIA`, Slack
`xox…`, SendGrid `SG.`), JWTs of 100 characters or more, and whitespace-bounded
opaque runs of 40 characters or more. Everything it replaces becomes the literal
`[redacted]`.
A fast pre-check bails out before allocating when no anchor character or keyword
is present, so the hot path on clean strings is cheap.
The full catalogue (patterns, order, thresholds, and what each rule does *not*
match) is in [Redaction rules](reference/rules.md).
## What it does not do
Worth knowing before you rely on it:
- **It is not a guarantee.** A bespoke credential format matches nothing, and a
secret shorter than the thresholds slips through.
- **JSON and YAML are not parsed.** `password=hunter2` is redacted;
`"password": "hunter2"` is not.
- **Header values are not redacted by `string`.** That is a separate, explicit
call; see [Redact HTTP headers](how-to/redact-http-headers.md).
- **It over-redacts.** A bare git commit hash is 40 characters and becomes
`[redacted]`.
The full list, with reasoning, is in [What rtb-redact does not
do](explanation/what-rtb-redact-does-not-do.md).
## Where it's wired
- `rtb-telemetry` applies `string` automatically to an event's `args` and
`err_msg` before an out-of-process sink serialises them. **`attrs` are not
covered**: callers own redaction for attribute values.
- `SENSITIVE_HEADERS` enumerates the headers to redact at DEBUG and is
available for an HTTP-client middleware to apply. Until a shared
client middleware exists in the framework, redact headers at the call
site.
- Any code emitting free-form strings to an external surface should
route through `string` first; see the
[how-to](how-to/redact-before-an-external-surface.md).
This crate complements `secrecy::SecretString` (which prevents *typed*
secrets from being formatted): `rtb-redact` is the safety net for strings
that were *assembled* and might contain a secret no type system caught.
## Where to go next
The documentation follows the [Diátaxis](https://diataxis.fr/) framework:
- **Tutorial:** [Scrub your first log line](tutorials/scrub-your-first-log-line.md):
fifteen minutes, from `cargo new` to a redacted connection string, including
what still gets through.
- **How-to guides**
- [Redact before an external surface](how-to/redact-before-an-external-surface.md)
- [Redact HTTP headers](how-to/redact-http-headers.md)
- [Cover a secret the rules miss](how-to/cover-a-secret-the-rules-miss.md)
- **Reference**
- [API reference](reference/api.md)
- [Redaction rules](reference/rules.md)
- **Explanation**
- [Why rtb-redact matches shapes, not values](explanation/why-shape-matching.md)
- [What rtb-redact does not do](explanation/what-rtb-redact-does-not-do.md)
## How this differs from the Go module
`gitlab.com/phpboyscout/go/redact` solves the same problem for the Go toolkit,
and the two are **not** bug-for-bug identical. The differences you are most
likely to notice:
| Opaque-run threshold | 40 characters, so a git SHA-1 is redacted | 41 characters, so a git SHA-1 survives |
| Replacement marker | `[redacted]` everywhere | `<redacted>`, `***` and `<redacted-token>`, by rule |
| Provider match | whole token replaced | prefix kept, body masked (`sk-***`) |
| Formats covered | no `github_pat_`, `glrt-`, `gldt-` | all three covered |
| JSON credential values | not matched | matched by a dedicated rule |
| Credential names in `name=value` | no left word boundary, so `csrf_token=` and `passwd=` redact; `key=`, `secret_key=`, `private_key=`, `access_key=`, `authorization=` and `?code=` do not | a word-bounded list covers the first five and a query-only rule covers `?code=`; `csrf_token=` and `passwd=` stay in the clear |
| Runtime dependencies | `regex`, `phf` | standard library only |
Do not assume a line redacted by one comes out the same from the other. The
credential-name row was checked on 2026-10-07 against `go/redact`'s main
branch, whose shared name list had not yet reached a tagged release.
## Further reading
The blog carries a curated route through this subject: **[Rust, and what survived the port](https://phpboyscout.uk/topics/rust/)** collects
everything written about it, ordered so you can start at the beginning rather
than newest-first.
!!! tip "Ask phpbotscout"
{ width="84" align=left style="border-radius:10px;margin-right:1rem" }
He answers questions about the projects over on the Discord, citing the docs
where they already cover it, and offering to raise an issue where they don't.
Bring a bug, an idea, or a questionable engineering decision.
[Join the Discord](https://discord.gg/mQzGbmGyzZ){ .md-button .md-button--primary }