Skip to content

Redaction pattern reference

The complete, normative list of what redact.String matches and what it puts in its place. Every pattern below is quoted verbatim from redact.go; if this page and the code ever disagree, the code is right and this page is a bug.

There is no way to add, remove, or reorder these rules — see What redact does not do. For why the catalogue is shaped this way, see Why the rule catalogue looks like this.

The order rules are applied in

String runs eight kinds of rule in a fixed order, each operating on the output of the last:

# Rule Replacement
1 URL userinfo <redacted>
2 Credential assignments (name=value) ***
3 JSON credential fields ***
4 Authorization headers in free text ***
5 AWS secret-key assignments ***
6 JSON Web Tokens <redacted-token>
7 Well-known provider prefixes (11 patterns, in table order) <prefix>***
8 The long-opaque-token fallback <redacted-token>

Order is load-bearing in one direction only: the specific, high-confidence rules run before the broad fallback, so a secret a named rule already masked is never re-examined. That is why token= followed by a 41-character value comes back as token=*** and not token=<redacted-token> — rule 2 got there first.

URL userinfo

regexp.MustCompile(`([A-Za-z][A-Za-z0-9+.\-]*://)[^/\s:@]*:[^/\s@]+@`)
// replacement: "${1}<redacted>@"

Matches the user:password@ component of a URL for any RFC 3986 scheme, not just HTTP. The scheme is captured and written back verbatim, so the shape of the URL survives redaction.

https://admin:hunter2@api.example.com/v1  →  https://<redacted>@api.example.com/v1
postgres://svc:s3cret@db.internal:5432/x  →  postgres://<redacted>@db.internal:5432/x
redis://:hunter2@cache.internal:6379      →  redis://<redacted>@cache.internal:6379
mongodb+srv://u:p@cluster.example/db      →  mongodb+srv://<redacted>@cluster.example/db
ldap://cn=admin:pw@host                   →  ldap://<redacted>@host

Connection strings in dial errors are the single most common shape of leaked credential, which is why the scheme is left open rather than restricted to http/https.

The username half may be empty (redis://:pass@host matches) but the colon and the password may not. These are not matched:

Input Why not
user:pass@host No ://, so bare a:b@c prose stays intact
https://user@host/path No :password — a token used as the whole userinfo is not caught here
https://:@host/path Password is empty
file:///etc/passwd No userinfo at all

Credential assignments (name=value)

regexp.MustCompile(`(?i)\b(apikey|api_key|key|access_token|refresh_token|token|secret|password|auth|authorization|signature)=([^&\s]+)`)
// replacement: "$1=***"

Case-insensitively matches one of these names followed by =, and masks the value while keeping the name:

apikey, api_key, key, access_token, refresh_token, token, secret, password, auth, authorization, signature

Despite the "query parameter" framing this rule is not limited to URLs. It fires on any name=value in free text, which is deliberate — the same names turn up in command lines and environment dumps:

POST /v1?apikey=sk-abc123&user=bob     →  POST /v1?apikey=***&user=bob
--api-key=sk-abc123 --verbose          →  --api-key=*** --verbose
login?user=bob&password=hunter2        →  login?user=bob&password=***

The value runs to the next whitespace or &, and nothing else. Any other delimiter is swallowed with the value, so redaction can mangle surrounding structure:

Input Output
Cookie: token=abc; theme=dark Cookie: token=*** theme=dark — the ; goes too
msg="key=abc" other=1 msg="key=*** other=1 — the closing quote goes too
token=abc) token=*** — the ) goes too

The name must start on a word boundary, so a credential name glued to a longer word is not matched, and one separated by a hyphen or a space is:

Input Output
sort_key=name unchanged — _ is a word character, so there is no boundary
sort-key=name sort-key=*** — a false positive
monkey=banana unchanged
primary key=abc primary key=***

= is required. api_key: abc123 — the YAML or ini spelling — is not matched by this rule, and only aws_secret_access_key and secret_access_key have a rule that accepts a colon.

JSON credential fields

regexp.MustCompile(`(?i)("(?:apikey|api_?key|access_token|refresh_token|token|secret|client_secret|password|auth|authorization|signature)"\s*:\s*")([^"]+)"`)
// replacement: `${1}***"`

Matches a quoted "key": "value" pair whose key is one of the names above, and replaces the value with ***. The key, colon and opening quote are captured and written back, and the closing quote is restored, so a body whose values contain no escaped quotes stays well-formed JSON. OAuth token-endpoint responses quoted inside HTTP client errors are the shape this exists for.

The key list is the assignment list plus client_secret, and it matches apikey and api_key but not api-key.

{"access_token":"ya29.a0abc","token_type":"Bearer"}  →  {"access_token":"***","token_type":"Bearer"}
{"password": "x"}                                    →  {"password": "***"}
{"API_Key":"abc12345"}                               →  {"API_Key":"***"}
{"client_secret":"cs_abc123def"}                     →  {"client_secret":"***"}
{"api-key":"abc"}                                    →  unchanged
{"name":"bob","token_type":"bearer"}                 →  unchanged

Whitespace either side of the colon is allowed. Only the value is masked — key names are never touched, which is what keeps a redacted body readable.

This is a text pattern, not a JSON parser. A value containing an escaped quote ("secret":"a\"b") ends the match at that quote and leaves the remainder of the value in the output.

Authorization headers in free text

regexp.MustCompile(`(?i)(authorization:\s*)(bearer|basic|digest|apikey)\s+([A-Za-z0-9._~\-+/=]+)`)
// replacement: "${1}$2 ***"

Matches an authorization: header written into a log line or error message, followed by one of four schemes — Bearer, Basic, Digest, ApiKey — and masks the credential while keeping the header name and the scheme, so you can still see which auth failed.

Authorization: Bearer abc123def456        →  Authorization: Bearer ***
Authorization: Basic dXNlcjpwYXNz         →  Authorization: Basic ***
Proxy-Authorization: Basic dXNlcjpwYXNz   →  Proxy-Authorization: Basic ***
Authorization:Bearer abc123def456         →  Authorization:Bearer ***

Proxy-Authorization matches because the pattern is unanchored and proxy-authorization: ends in authorization:.

These are not matched:

Input Why not
Authorization: token abc123 token is not one of the four schemes
authorization : Bearer abc Whitespace before the colon is not allowed
BEARER abc123def456 The authorization: prefix is required
X-API-Key: abc123def456 This rule only knows authorization:

That last row is the important one. String does not mask arbitrary header values — see Does String mask HTTP header values? and the sensitive header reference.

AWS secret-key assignments

regexp.MustCompile(`(?i)(aws_secret_access_key|secret_access_key)(\s*[=:]\s*)\S+`)
// replacement: "${1}${2}***"

AWS secret access keys carry no distinguishing prefix, so they are matched by the name they are assigned to rather than by their value — matching a bare 40- character string would also scrub every git SHA-1 in your logs. The name and the separator (which may be = or :, with optional surrounding whitespace) are preserved.

aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/…    →  aws_secret_access_key=***
AWS_SECRET_ACCESS_KEY = wJalrXUtnFEMI/K7MDENG/…  →  AWS_SECRET_ACCESS_KEY = ***
secret_access_key : abc def                      →  secret_access_key : *** def

The value is \S+ — everything to the next whitespace. A secret containing a space is only partly masked; a bare aws_secret_access_key with nothing assigned is left alone.

JSON Web Tokens

regexp.MustCompile(`eyJ[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+\.[A-Za-z0-9_\-]+`)
// replacement: "<redacted-token>"

Matches the three-segment base64url shape beginning with the distinctive eyJ header prefix (which is {" base64-encoded) and replaces the whole token — unlike the provider prefixes, there is no fragment worth keeping. It catches bare Bearer eyJ… tokens that the Authorization-header rule misses because they carry no authorization: prefix.

Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.dozjgNryP4J  →  Bearer <redacted-token>

All three segments are required, each at least one character: a two-segment eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0 is not matched.

Only the URL-safe base64 alphabet is recognised. Standard base64's + and /, and = padding, are outside the character class, and a token that uses them fares badly in either of two ways — a + inside a segment stops the token matching at all, and a / in the final segment ends the match early, leaving the tail in the clear:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIi+OiIxIn0.abc   →  unchanged
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.ab/c=  →  <redacted-token>/c=

JWTs are specified to use the URL-safe alphabet, so this bites only on a malformed or re-encoded token.

Well-known provider prefixes

Eleven patterns whose prefix is unambiguous enough that a match is almost certainly a real credential. Each keeps its literal prefix for debug readability and replaces the rest with ***.

The number of characters kept is anchored to the known prefix — it is a constant per pattern, never measured from the matched text. That is what stops a token whose body happens to contain _ or - from leaking a fragment of itself.

Provider Pattern Minimum body Redacts to
OpenAI / Anthropic-style sk-[A-Za-z0-9_\-]{16,} 16 sk-***
GitHub PAT (classic) ghp_[A-Za-z0-9]{30,} 30 ghp_***
GitHub OAuth gho_[A-Za-z0-9]{30,} 30 gho_***
GitHub app server ghs_[A-Za-z0-9]{30,} 30 ghs_***
GitHub fine-grained PAT github_pat_[A-Za-z0-9_]{30,} 30 github_pat_***
GitLab personal access token glpat-[A-Za-z0-9_\-]{16,} 16 glpat-***
GitLab runner token glrt-[A-Za-z0-9_\-]{16,} 16 glrt-***
GitLab deploy token gldt-[A-Za-z0-9_\-]{16,} 16 gldt-***
Slack xox[baprs]-[A-Za-z0-9-]{10,} 10 xoxb-*** (etc.)
Google API key AIza[A-Za-z0-9_\-]{30,} 30 AIza***
AWS access key ID AKIA[A-Z0-9]{16} exactly 16 AKIA***

The minimum body length is a hard floor, not a hint. sk- followed by 15 characters is left in the clear; 16 is redacted. AKIA requires exactly 16 uppercase characters, matching the real AWS access-key-ID format — AKIAIOSFODNN7EXAMPL (15) passes straight through.

The Slack pattern covers xoxb, xoxa, xoxp, xoxr and xoxs only. Other Slack token families, xoxc among them, are not matched.

The long-opaque-token fallback

regexp.MustCompile(`\b[A-Za-z0-9_\-]{41,}\b`)
// replacement: "<redacted-token>"

Anything still left that is a single run of 41 or more characters from [A-Za-z0-9_-] is replaced whole. This is the catch-all for high-entropy secrets with no recognisable prefix, and it runs last so it only ever sees what the named rules did not claim.

The threshold clears the opaque strings that legitimately appear in error text:

Value Length Redacted?
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

Because - is inside the character class but is not a word character, two adjacent 41-character runs joined by a hyphen are treated as one token and replaced with a single <redacted-token>.

Replacement tokens

Token Length Used for
<redacted> 10 URL userinfo
*** 3 Assignment values, JSON values, Authorization credentials, AWS secret values, provider-prefix bodies
<redacted-token> 16 JWTs and the long-opaque-token fallback

None of these re-matches any rule, which is what makes String idempotent. They are not configurable, and there is no marker distinguishing "a rule fired here" from a literal *** that was in the input already.