Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

secret-redact

redact secrets from logs by key, by path, and by what the value actually looks like.

npm i secret-redact

why

path based redaction only catches what you predicted:

pino({ redact: ['password', 'user.token'] })

it does not catch a seed phrase pasted into a support note, a JWT inside an error message, or an api key in a url. those are the ones that hurt.

use

import { redact } from 'secret-redact'

redact({
  user: 'alice',
  password: 'hunter2',
  note: 'cook voyage document eight skate token alien guide drink uncle term abuse'
})
// { user: 'alice', password: '[redacted]', note: '[redacted]' }

it never mutates the input. you get a clean copy.

detectors

detector catches
mnemonic 12/15/18/21/24 word bip39 phrases
private-key -----BEGIN … PRIVATE KEY-----
hex-key 64 hex chars, with or without 0x
jwt eyJ….….…, scrubbed in place
bearer Bearer <token>, keeps the word
api-key npm_ ghp_ sk- sk_live_ AKIA xox[bp]-
card-number 13–19 digits, luhn checked so order ids do not trip it

detectors that can locate the match only replace that part:

redactString('auth failed for eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIn0.sig then retried')
// 'auth failed for [redacted] then retried'

keys and paths too

redact(data, {
  keys: ['internalId'],              // added to the built-in list
  paths: ['items.*.card', 'user.ssn']
})

built-in keys cover password secret token authorization cookie mnemonic seed pin cvv ssn signature and friends. matching ignores case and _/-, so API_KEY, apiKey and api-key are the same key.

Secret<T>

for values you hold in memory rather than log:

import { secret } from 'secret-redact'

const apiKey = secret(process.env.API_KEY, 'api key')

console.log(apiKey)          // [api key]
`${apiKey}`                  // '[api key]'
JSON.stringify({ apiKey })   // {"apiKey":"[api key]"}

apiKey.expose()              // the real value, and it greps

the value is a #private field, so it survives spread, Object.keys and JSON.stringify.

wrap a logger

import { guard } from 'secret-redact'

const log = guard(console)
log.info('login', { user: 'alice', password: 'hunter2' })
// login { user: 'alice', password: '[redacted]' }

safe on real data

circular references become [circular], depth is capped, Error keeps its name and stack but scrubs the message, and Map/Set/Date are handled.

one deliberate false positive

hex-key flags any 64 hex chars — which includes transaction hashes. that is on purpose. a 32 byte hex blob in a log is not worth the risk of it being a key. pass detectors yourself if you disagree.

api

redact(value, opts?) deep copy, redacted
redactString(str, opts?) detectors only
secret(value, label?) Secret<T>
guard(logger, opts?) wraps every method
DEFAULT_DETECTORS the array, to extend or filter

zero dependencies. types included.

licence

MIT

About

redact secrets from logs by path and by detector. catches seed phrases, keys, jwts and cards

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages