Skip to content

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

func String(s string) 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 idempotentString(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)+64 bytes.

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

func Error(err error) string

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

var SensitiveHeaderKeys []string

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

func IsSensitiveHeaderKey(name string) bool

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:

  1. the trimmed, lowercased name exactly equals an entry of SensitiveHeaderKeys; or
  2. the name contains the whole word auth, token, key, secret, bearer, password or credential, or the substring authorization.
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.