API reference¶
Every exported symbol in gitlab.com/phpboyscout/go/redact, what it returns, and
how it behaves at the edges. There are four, and the package exports nothing
else — no options struct, no constructor, no interface.
func String(s string) string
func Error(err error) string
func IsSensitiveHeaderKey(name string) bool
var SensitiveHeaderKeys []string
The package has no configuration surface: no functional options, no environment variables, no config file, no CLI. Behaviour is fixed at compile time. See What redact does not do for the consequences.
func String¶
Applies every redaction rule to s and returns the sanitised copy. The input is
never mutated — Go strings are immutable, and String builds a new one.
| Input | Returns |
|---|---|
"" |
"" — the empty string short-circuits before any pattern runs |
| A string with no match | The input, unchanged and byte-identical |
| A string with one or more matches | A copy with each match replaced |
| Invalid UTF-8 | Handled without panic; the invalid bytes are left alone and ASCII rules still fire around them |
The rules, their order, and exactly what each replaces are in the redaction pattern reference.
Guarantees String holds¶
Three invariants are asserted by FuzzRedactString in redact_fuzz_test.go:
- It never panics, on any input, including invalid UTF-8 and control bytes.
- It is idempotent —
String(String(s)) == String(s). None of the replacement strings (<redacted>,***,<redacted-token>) re-matches a rule, so a string that was already redacted upstream passes through a second boundary unharmed. This is what makes defensive redaction free. - It cannot blow up the size of a log line. Each replacement is a short
fixed string, so growth is proportional to the number of matches rather than
explosive; the fuzz guard fails if output exceeds
4*len(input)+64bytes.
This is a bound on a multiple of the input, not on a constant. A string that
is nothing but short matches does grow: 200 repetitions of key=a (1,200
bytes) come back as 1,600 bytes, because each 5-byte match becomes a 7-byte
one.
Cost of calling String¶
Every pattern is compiled once at package init with regexp.MustCompile and uses
Go's RE2 engine, which has no backtracking. Matching is linear in the length of
the input, so an adversarial string cannot cause catastrophic backtracking —
there is no ReDoS exposure. String makes 18 passes over the string (one per
rule, one per provider prefix), so treat it as a boundary call rather than
something to run in a tight loop.
func Error¶
Convenience wrapper, exactly equivalent to String(err.Error()).
| Input | Returns |
|---|---|
nil |
"" — no guard needed at the call site |
| A non-nil error | String(err.Error()) |
Wrapping is irrelevant: err.Error() already flattens a %w chain into one
string, so a redacted wrapped error keeps its full context.
base := errors.New("dial https://u:p@h/x")
wrapped := fmt.Errorf("startup: %w", base)
redact.Error(wrapped)
// startup: dial https://<redacted>@h/x
Error returns a string, not an error. It is for handing an error to a
logger, a span, or a report — not for propagating one. Redacting an error you
intend to errors.Is or errors.As against would destroy the chain, so redact
at the point of output only.
var SensitiveHeaderKeys¶
The curated list of HTTP header names whose values should be masked before they are logged or shipped. The full contents, and how they compare with the wider fuzzy match, are in the sensitive header reference.
Treat SensitiveHeaderKeys as read-only¶
It is an exported slice, so the compiler will let you append to it or replace it. Doing so does not do what it looks like it does.
IsSensitiveHeaderKey consults a lowercased lookup map that is built once, at
package init, from the slice's initial contents. Mutating the slice afterwards
has no effect on that map:
redact.SensitiveHeaderKeys = append(redact.SensitiveHeaderKeys, "X-Widget")
redact.IsSensitiveHeaderKey("X-Widget") // still false
redact.SensitiveHeaderKeys = nil
redact.IsSensitiveHeaderKey("Authorization") // still true
Mutation is also a data race if anything else reads the slice concurrently. If you need a different set, build your own from a copy — see Choosing a stricter predicate.
func IsSensitiveHeaderKey¶
Reports whether name identifies a header whose value should be redacted before
logging. It answers "is the operator likely to have put a secret in this
header?", which is deliberately a wider question than "is this on the curated
list?".
name is trimmed of surrounding whitespace and lowercased before matching, so
http.Header's canonical form (X-Api-Key), a raw wire name (x-api-key), and
a padded one (" Authorization ") all behave the same. It returns true when
either:
- the trimmed, lowercased name exactly equals an entry of
SensitiveHeaderKeys; or - the name contains the whole word
auth,token,key,secret,bearer,passwordorcredential, or the substringauthorization.
| Input | Result | Why |
|---|---|---|
"Authorization" |
true |
Exact list entry |
" Authorization " |
true |
Whitespace is trimmed first |
"X-Custom-Auth" |
true |
Whole word auth |
"X-Amz-Security-Token" |
true |
Whole word token |
"Public-Key-Pins" |
true |
Whole word key — a false positive, and the safe direction to be wrong in |
"" |
false |
Empty short-circuits |
"Content-Type" |
false |
No credential word |
"X-Request-ID" |
false |
No credential word |
"X-Apikey" |
false |
apikey is one word; the pattern wants key on its own |
"Www-Authenticate" |
false |
authenticate is not authorization, and there is no whole-word auth |
"Cookie2" |
false |
Not an exact list entry, and no credential word |
Hyphens count as word separators, so X-API-Key matches on key while
X-Apikey does not. If you need the glued spellings caught, test for them
yourself in addition to calling this function.
IsSensitiveHeaderKey inspects the header name only. It never looks at the
value, and it does not redact anything — masking the value is the caller's job.
String does not do it for you either: see
Does String mask HTTP header values?.
Runnable examples and the generated API¶
pkg.go.dev carries the generated Go documentation and the runnable examples. This page is the prose reference: the edge cases, defaults, and failure modes that a signature list cannot state.