Stop scrubbing logs. Make your secrets unloggable.
How many countless hours have you wasted trying to find the cause of bugs in code without proper instrumentation? Bugs that would be trivial if only someone had logged the inputs, outputs and errors of the right function at the right time?
I'm over it. I'd rather build things than spend my day chasing heisenbugs.
That's why I log everything. Every request that comes in, every call to my business logic, every database request and every error or interesting event that happens along the way. I log it all – including and especially function arguments.
If bad inputs have slipped through the validation net, I want to know about it now.
Here's the problem: running applications are full of secrets. API keys, database credentials, pre-hash user passwords. They're in there, waiting to leak into nefarious hands.
Speaking of hands – hands up if you've ever accidentally logged a secret? I have. It's a pain in the ass. You have to fix the offending code and scrub the record clean.
This makes us avoid logging the fat, information-rich structs that get passed around our program. Useful as that might be for debugging, it's a security risk just waiting to spoil your day.
And who has time to manually implement
fmt.Stringer
for every argument they might want to log? Not me.
That's why I've released logfusc
, a Go package that gives you bomb-proof
secret redaction for logs and traces.
Stop scrubbing logs in the aftermath of preventable human errors. Stop
hand-coding string
representations of must-log structs just to keep their
privates private. Make secrets unloggable.
logfusc.Secret
is a generic wrapper for any type that you want to redact from
logs, traces and other outputs.
Secret
implements fmt.Stringer
and fmt.GoStringer
, so no matter how hard
you try to format it, it doesn't give up its secret.
password := "do not log!"
secret := logfusc.NewSecret(password)
fmt.Printf("%s\n", secret)
// => logfusc.Secret[string]{REDACTED}
fmt.Printf("%q\n", secret)
// => "logfusc.Secret[string]{REDACTED}"
fmt.Printf("%v\n", secret)
// => "logfusc.Secret[string]{REDACTED}"
fmt.Printf("%+v\n", secret)
// => "logfusc.Secret[string]{REDACTED}"
fmt.Printf("%#v\n", secret)
// => "logfusc.Secret[string]{REDACTED}"
fmt.Printf("%x\n", secret)
// => 6c6f67667573632e5365637265745b737472696e675d7b52454441435445447d == logfusc.Secret[string]{REDACTED}
logfusc.Secret
redacts your secrets when marshaled to a variety of formats, so
you can pass complete structs to your logger without worrying about leaking
sensitive data. No more manual redaction. No configuration. No leaks.
type Universe struct {
SecretOfLife logfusc.Secret[int] `json:"secret_of_life"`
}
func main() {
universe := Universe{
SecretOfLife: logfusc.NewSecret(42),
}
b, _ := json.Marshal(universe)
log.Println(string(b))
}
// => {"name":"alice","secret_of_life":"logfusc.Secret[int]{REDACTED}"}
So far, Secret
satisfies:
json.Marshaler
(tested with both encoding/json
and json-iterator)Protect secrets at the boundaries of your service by decoding them directly into
logfusc.Secret
.
type RegisterRequest struct {
Email string `json:"email"`
Password logfusc.Secret[string] `json:"password"`
}
func Register(w http.ResponseWriter, r *http.Request) {
var req RegisterRequest
_ = json.NewDecoder(r.Body).Decode(&req)
fmt.Println(req.Password)
fmt.Println(req.Password.Expose())
}
// => logfusc.Secret[string]{REDACTED}
// password
So far, Secret
satisfies:
json.Unmarshaler
Secret
encourages you to log often and trace everything in the knowledge
that your secrets are safe. That doesn't stop you working with sensitive data,
but you have to do it deliberately.
secret := logfusc.NewSecret("PEM string")
log.Println(secret.Expose())
// => PEM string
logfusc.Secret
should work as described with all sane logging libraries.
"Should" because, in Go, you can technically access the private fields of a
struct using a combination of the reflect
and unsafe
packages. If your
logger is doing this, you need a new one.
For your peace of mind, logfusc.Secret
has been explicitly tested for
compatibility with the following loggers:
log
logrus
zap
zerolog
logfusc
for yourself!Go forth and log freely! You can find logfusc
on GitHub now.