My logging library redacted fields named monkey, keyboard, and author. It matched sensitive names as substrings, so ordinary field names got caught too.
The function walks an object and replaces values with [REDACTED] when their field names match a configured list. The defaults were password, token, secret, key, and auth. This was the matching rule:
const shouldFilter = sensitiveKeys.some((sensitiveKey) =>
key.toLowerCase().includes(sensitiveKey.toLowerCase())
);
monkey contains key. author contains auth. The function couldn’t distinguish those names from fields intended to hold credentials.
That’s a problem when you’re trying to make sense of a log. A value you expected to see is gone, and the replacement looks just like an intentionally hidden secret.
Word boundaries fix part of it
The replacement splits field names into tokens at casing changes and separators, then matches those tokens instead of arbitrary substrings.
That leaves monkey intact as one token. It doesn’t match key. Meanwhile, apiKey becomes api and key, so its value is still redacted.
But apikey, written entirely in lowercase, also stays as one token. Splitting at casing and separator boundaries doesn’t identify the key inside it. With only the original default list, the new matcher would leave that field unredacted.
The change therefore needed explicit entries for joined credential names. The default list now includes authorization, apikey, authtoken, accesstoken, and secretkey alongside the original five.
These aren’t redundant spellings. They preserve specific credential cases that substring matching previously caught. A test showing that monkey is no longer redacted only checks half of the change. Another test needs to show that apikey still is.
The PR’s verification covered credential-shaped names, unrelated names, and caller-supplied keys. The tested credential cases retained their redaction behavior. That’s useful evidence, but it isn’t a guarantee about every possible name someone might use for a secret.
Custom keys need the same treatment
The function also lets callers supply their own sensitive names. Those names need to go through the same tokenization as the fields being checked.
For example, consider a configured key of creditCard. Splitting only the field produces credit and card, neither of which equals the unsplit configuration value. The value could pass through unredacted even though the caller explicitly asked to hide it.
Both sides are tokenized now. For a multi-token configured key, every token must appear among the field’s tokens. A field containing only card isn’t enough to match creditCard.
The implementation also compares joined forms, so a configured apiKey can match a field spelled apikey, and handles plural forms such as keys. Those details make the matching behavior more consistent across naming conventions without returning to unrestricted substring matching.
I don’t think the regex needs to be the main story here. The important part is what callers can expect: their configuration should work across supported spellings, and unrelated names shouldn’t disappear just because they contain a few matching letters.
Keep both sets of regression tests
This kind of fix needs tests for values that should remain visible and values that must stay hidden. Otherwise, it’s easy to improve the examples from a bug report while breaking behavior that wasn’t mentioned in it.
For this change, the useful questions are concrete:
- Does
monkeyremain visible? - Do
apiKeyandapikeyboth remain redacted? - Does a caller-supplied
creditCardwork across casing and separators? - Does a partial match avoid hiding an unrelated field?
Field-name matching still has limits. It doesn’t inspect a value and determine whether it’s a credential. The configured names and matching rules define what it catches.
I want fewer false positives, but the credential cases need to keep passing. Those tests belong beside the examples of fields we want to leave alone.
Sources
- logan-logger-ts #79 — the word-boundary change and verification results
- logan-logger-ts #61 — the original report
- commit 0585989 — implementation and regression tests
- treering #4 — the tokenization and matching specification
I’d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at @[email protected].