The rule catalogue & its limits¶
What
Stringactually does. This page sets out every patternredact.Stringapplies, what each one replaces, the reasoning behind the conservative opaque-token fallback, and — just as importantly — what the catalogue does not catch.
Every claim here is drawn directly from the patterns in redact.go. All
patterns are compiled once at package init and use Go's RE2 engine — no
backtracking, so matching is linear in the length of the input.
How String is applied¶
redact.String runs its rules in a fixed order over the input, each pass
operating on the output of the last:
- URL userinfo
- Credential query parameters
- Authorization-header tokens
- AWS secret-key assignments
- JSON Web Tokens
- Well-known provider prefixes (each prefix in turn)
- The long-opaque-token fallback
Because the specific, high-confidence rules run before the broad fallback, a token that a named rule already masked is never re-examined by the fuzzy pass.
The rules¶
URL userinfo¶
Matches scheme://user:pass@ for http/https URLs and replaces the userinfo
with <redacted>, preserving the scheme and host so the URL stays recognisable.
Only http and https schemes are matched, and only the full user:pass@
form.
Credential query parameters¶
Case-insensitively matches these parameter names and masks the value (everything
up to the next & or whitespace) with ***, keeping the parameter name:
apikey, api_key, key, access_token, refresh_token, token, secret,
password, auth, authorization, signature.
The auth and authorization names are included here so a stray
?authorization=sk-abc123 is caught even when it does not also match the
Authorization-header rule below.
Authorization-header tokens¶
Case-insensitively matches an authorization: header written into free text
(an error message, a log line) followed by a scheme — Bearer, Basic,
Digest, or ApiKey — and masks the credential, keeping the header name and
the scheme:
AWS secret-key assignments¶
AWS secret access keys carry no distinguishing prefix, so they are matched by
their assignment form — aws_secret_access_key or secret_access_key
followed by = or : — rather than by value. Matching a bare 40-character value
would also scrub git SHA-1 hashes. The key name and separator are preserved; the
value becomes ***:
JSON Web Tokens¶
Matches the distinctive eyJ… three-segment base64url shape and replaces the
whole token with <redacted-token> — there is no meaningful prefix to keep.
The eyJ header prefix keeps false positives low, and this rule catches bare
Bearer eyJ… tokens that the Authorization-header rule would miss.
Well-known provider prefixes¶
These formats are unambiguous: the prefix followed by enough alphanumeric
content is almost certainly a real credential. Each match keeps its literal
prefix for debug readability and replaces the remainder with ***. Crucially,
the number of characters kept is anchored to the known prefix, never discovered
from the matched text — so a token body that happens to contain _ or -
cannot leak.
| Provider | Prefix matched | Redacts to |
|---|---|---|
| OpenAI / Anthropic-style | sk- + ≥ 16 chars |
sk-*** |
| GitHub PAT (classic) | ghp_ + ≥ 30 chars |
ghp_*** |
| GitHub OAuth | gho_ + ≥ 30 chars |
gho_*** |
| GitHub app server | ghs_ + ≥ 30 chars |
ghs_*** |
| GitHub fine-grained PAT | github_pat_ + ≥ 30 chars |
github_pat_*** |
| GitLab personal access token | glpat- + ≥ 16 chars |
glpat-*** |
| GitLab runner token | glrt- + ≥ 16 chars |
glrt-*** |
| GitLab deploy token | gldt- + ≥ 16 chars |
gldt-*** |
| Slack | xox[baprs]- + ≥ 10 chars |
xoxb-*** (etc.) |
| Google API key | AIza + ≥ 30 chars |
AIza*** |
| AWS access key ID | AKIA + 16 chars |
AKIA*** |
The long-opaque-token fallback¶
Anything left that is a single run of ≥ 41 characters from [A-Za-z0-9_-] is
replaced whole with <redacted-token>. This is the catch-all for high-entropy
secrets that carry no recognisable prefix.
Why 41 characters¶
The fallback threshold is deliberately high to keep false positives near zero. Common non-secret opaque strings are all shorter than 41 characters:
| Value | Length | Matched by the ≥ 41 fallback? |
|---|---|---|
| UUID (no hyphens) | 32 | No |
| MD5 hash | 32 | No |
| UUID (with hyphens) | 36 | No |
| SHA-1 / git commit hash | 40 | No |
| SHA-256 hash | 64 | Yes |
A UUID, an MD5 digest, or a git SHA — the identifiers that most often appear in error strings and log lines — sit at or below 40 characters and pass through untouched. SHA-256 (64 chars) will be redacted; that is an accepted tradeoff, on the grounds that raw hashes rarely appear in the kind of free-form strings this module sanitises, and masking one is harmless where leaking a 41-plus-char secret is not.
The threshold errs toward precision over recall: it would rather miss an unusually short opaque secret than corrupt a legitimate identifier.
Replacement tokens¶
String uses a small, fixed set of replacements, which keeps the output
bounded — it never pathologically grows the input:
| Token | Used for |
|---|---|
<redacted> |
URL userinfo |
*** |
query-parameter values, Authorization credentials, AWS secret values, provider-prefix bodies |
<redacted-token> |
JWTs and the long-opaque-token fallback |
Guarantees¶
String holds three invariants, enforced by fuzz testing:
- Never panics on any input.
- Idempotent —
String(String(s)) == String(s), so redacting an already-redacted string is safe. - Bounded growth — the output is never more than the input plus a small constant, so redaction cannot blow up the size of a log line.
Limitations¶
A pattern catalogue never reaches 100 % recall. Design around these gaps:
- Non-standard formats slip through. A bespoke or internal credential format the catalogue has never seen will not match. Where you handle such inputs, redact them upstream — do not rely on this module alone.
- Short opaque secrets slip through. A high-entropy secret shorter than 41 characters is only caught if it appears in a recognised assignment (a credential query parameter, an Authorization header, an AWS assignment) or carries a known prefix. A bare short token in free text will not match the fallback.
- ASCII only. Every pattern uses ASCII character classes. Virtually all real-world provider tokens are ASCII, and UTF-8 credentials in the wild are vanishingly rare — but a non-ASCII secret will not be matched.
- URL userinfo is HTTP(S) only. Userinfo in other schemes (e.g.
postgres://,redis://) is not matched by the userinfo rule, though a long enough token in one may still trip the fallback.
The takeaway is the same as the threat model: treat
redact.String as a boundary safety net that catches the common accidents, and
keep stripping known secrets at the source for anything it cannot recognise.
Related¶
- Threat model — why boundary redaction exists.
- Redact at the boundary — applying the rules in practice.