What redact does not do¶
redact is a small, fixed pattern matcher. Most of what people expect it to do,
it does not — usually on purpose. This page states the boundaries plainly so you
can decide what else you need, rather than discovering a gap from a leaked log
line.
The short version: it recognises shapes of credential in strings you hand it, in ASCII, with no configuration, and it is a safety net rather than a guarantee.
Can I add my own patterns?¶
No. There is no extension point of any kind — no options struct, no registration function, no way to supply a regexp. The pattern list is a set of unexported package variables compiled at init, and the exported surface is four symbols with no configuration between them.
If you need to catch a format the catalogue does not know, wrap it:
var internalTokenPattern = regexp.MustCompile(`ACME-[0-9]{12}`)
// safe applies our own rules first, then the shared catalogue.
func safe(s string) string {
return redact.String(internalTokenPattern.ReplaceAllString(s, "ACME-***"))
}
Order does not matter for correctness here, because String is idempotent and
none of its replacements re-matches. Putting your rule first simply means your
own format wins when both could match.
Can I configure redact with a config file or an environment variable?¶
No. redact reads nothing at runtime — no config file, no environment variable,
no flag, no CLI. It is a library with pure functions and no I/O. Behaviour is
fixed at the version you compile against, and the only way to change it is to
change the module.
That is deliberate: a redactor whose rules can be weakened by the environment is a redactor that can be switched off by accident, in exactly the deployment where you needed it.
Can I turn a rule off?¶
No. There is no way to disable a rule you consider over-eager, and there are a
few that can be. sort-key=name becomes sort-key=***; a SHA-256 hash is
replaced wholesale; a cookie header loses its ; delimiters. The
pattern reference lists each of these.
If a false positive is destroying something you need, do not reach for String
on that value at all — redact the parts of the string you know are sensitive and
leave the rest.
Does String mask HTTP header values?¶
Not in general. String recognises one header shape — authorization: followed
by Bearer, Basic, Digest or ApiKey — and nothing else. Every other header
line passes through untouched:
Authorization: Bearer abc123def456 → Authorization: Bearer ***
X-API-Key: abc123def456ghijklmno → unchanged
Cookie: session=deadbeef → unchanged
Dumping request headers through String and calling it safe is the mistake this
package's header symbols exist to prevent. Decide which headers to mask with
IsSensitiveHeaderKey, mask the values
yourself, and use String on the survivors as a second pass.
Does it catch short or bespoke secrets?¶
No. Two gaps sit here.
Bespoke formats. A credential in an internal format the catalogue has never seen — a service-specific token, an internal ID scheme — matches nothing. There is no entropy analysis and no learning; recognition is entirely by shape.
Short opaque secrets. The catch-all fallback needs 41 characters. A
high-entropy secret shorter than that is caught only if it appears in a
recognised assignment (token=, a JSON credential field, an Authorization
header, an AWS assignment) or carries a known provider prefix. A bare short token
sitting in prose matches nothing.
The provider prefixes have their own floors, and they are hard: sk- needs 16
body characters, ghp_ needs 30, AKIA needs exactly 16 uppercase ones. One
character short and the credential is left in the clear.
Does it work on non-ASCII secrets?¶
No. Every pattern uses ASCII character classes, so a credential built from non-ASCII characters is not matched at any length:
String handles non-ASCII input safely — it never panics, invalid UTF-8
included, and ASCII rules still fire around the non-ASCII text — it simply will
not recognise a non-ASCII secret. Real-world provider tokens are ASCII virtually
without exception, which is why the tradeoff is accepted rather than fixed.
Does it understand JSON, YAML, or logfmt?¶
No. Every rule is a regular expression over text; nothing is parsed. Three consequences follow.
JSON is matched by shape, not structure. The rule matches
"key"\s*:\s*"value" pairs. A value containing an escaped quote
({"secret":"a\"b"}) ends the match at that quote and leaves the rest of the
value in the output.
YAML and ini assignments are mostly not covered. api_key: abc123 is not
matched — the assignment rule requires =. Only aws_secret_access_key and
secret_access_key have a rule that accepts a colon.
Delimiters can be swallowed. An assignment value runs to the next whitespace
or &, so any other delimiter goes with it: msg="key=abc" other=1 becomes
msg="key=*** other=1, closing quote and all. Redacted output is meant for a
human reading a log line, not for a parser.
Can I recover the original string?¶
No. Redaction is one-way and lossy. <redacted>, *** and <redacted-token>
are fixed literals carrying no encoding of what they replaced — no hash, no
length, no identifier. There is no key, no reversal function, and nothing to
correlate two occurrences of the same secret.
If you need to know that two log lines mention the same credential, you need a stable derived identifier, and you must compute it before redaction and log that instead.
Does it scrub secrets from memory?¶
No. String returns a new string; the original stays in memory, reachable, until
the garbage collector gets to it. Go strings are immutable and cannot be zeroed
in place.
redact is about what leaves the process, not about what sits inside it. Keeping
a secret out of a heap dump or a core file is a different job with different
tools.
Is it a secret scanner?¶
No. It does not read files, walk a repository, scan git history, or report findings. It rewrites one string at a time and tells you nothing about what it found — there is no count, no error, no flag indicating a rule fired. If you need to know whether a string contained a secret, compare the output with the input yourself.
Is it a compliance control?¶
No, and treating it as one is the failure mode this package is most likely to enable. A pattern catalogue never reaches 100% recall, and the tradeoffs here lean deliberately toward precision — the 41-character floor exists so UUIDs and git SHAs survive, which necessarily means shorter secrets do too.
Boundary redaction is the last line of defence. It catches the accidents you did not anticipate: a token an upstream API echoed back inside a 401 body, a connection string a dial error quoted. It is not a substitute for not putting the secret in the string, and it will not tell you when it has missed one.
Related¶
- Why the rule catalogue looks like this — the reasoning behind the tradeoffs above.
- Redaction pattern reference — exactly what each rule matches.
- Threat model — what boundary redaction is defending.