npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

autoredact

v1.0.0

Published

A JSON logger that strips secrets and PII out of every record.

Readme

autoredact

A JSON logger that strips secrets and PII out of every record.

Why this matters

Your logs end up in places you do not control. A Lambda console.log({ req }) writes every Authorization header into CloudWatch for the length of the retention policy. Sentry.captureException(err, { extra: payload }) ships that payload to Sentry's servers. A Datadog agent indexes your stdout JSON field by field, so an sk_live_ Stripe key or a cookie value lands in a search index the whole workspace can query. Once a credential reaches any of those, "rotate" is the only safe response.

Installation

npm install autoredact
pnpm add autoredact
yarn add autoredact

Usage

Zero configuration required.

import { createLogger } from 'autoredact'

const logger = createLogger()

// Sensitive keys redact regardless of where they sit in the tree.
logger.info({ user: { id: 'u_1', api_key: 'sk_live_xxx', email: '[email protected]' } }, 'user signed in')
// {
//   "time": "...",
//   "level": "info",
//   "msg": "user signed in",
//   "user": {
//     "id": "u_1",
//     "api_key": "[REDACTED]",
//     "email": "[email protected]"
//   }
// }

// Credentials buried inside a free form string are caught by shape, not by path.
logger.info({ note: 'rotated AKIAIOSFODNN7EXAMPLE yesterday' })
// {
//   "time": "...",
//   "level": "info",
//   "note": "rotated [REDACTED] yesterday"
// }

// Errors get scrubbed inside both message and stack, even though the surrounding
// keys are 'message' and 'stack', not 'databaseUrl'.
logger.error(new Error('cannot connect to postgres://app:[email protected]/mydb'))
// {
//   "time": "...",
//   "level": "error",
//   "msg": "cannot connect to [REDACTED]",
//   "err": {
//     "name": "Error",
//     "message": "cannot connect to [REDACTED]",
//     "stack": "Error: cannot connect to [REDACTED]\n    at ..."
//   }
// }

How it works

What gets redacted by key name

About thirty default phrases plus their snake, camel, and kebab variants. The full default list:

password, passwd, pwd, passphrase, secret, secrets, credential, credentials, token, tokens, bearer, refresh_token, id_token, access_token, authorization, auth, cookie, cookies, set-cookie, api_key, access_key, private_key, client_secret, session, otp, mfa, totp, ssn, sin, credit_card, card_number, cvv, cvc, cvv2, tax_id, ein, iban, routing_number, pin, pincode.

The tokenizer splits keys at camel boundaries, underscores, and dashes, so userApiKey, user_api_key, and x-api-key all match the phrase ['api', 'key'] once. There is no per case duplication in the deny list.

What gets redacted by value shape

Patterns scan every string value in the tree, including string values inside Error messages and stack traces:

  • eyJ... JWT (three base64url segments)
  • AKIA... AWS access keys, ASIA... AWS STS tokens
  • sk_live_..., sk_test_..., pk_live_..., rk_... Stripe keys
  • ghp_..., gho_..., ghu_..., ghs_..., ghr_... GitHub classic tokens
  • github_pat_... GitHub fine grained PATs
  • xox[a-z]-... Slack tokens
  • -----BEGIN ... PRIVATE KEY----- PEM blocks
  • Bearer <token> in free text
  • protocol://user:password@host database connection strings
  • 13 to 19 digit numbers that pass the Luhn checksum (credit card numbers)

The Luhn gate keeps the credit card pattern from redacting innocent digit runs (order ids, invoice numbers).

API

createLogger(options?)

const logger = createLogger({
  level: 'info', // 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'silent'
  transport: 'json', // 'json' | 'pretty' | (line, record) => void
  redact: { extraPhrases: [['hmac']] },
  base: { service: 'api' },
})

logger.info('msg')
logger.info({ obj: 1 }, 'msg')
logger.error(error)
logger.error(error, 'context')

const child = logger.child({ requestId })
logger.isLevelEnabled('debug')

The logger is generic on the binding shape. The base option's shape threads through every record and every child:

const logger = createLogger({ base: { service: 'api', region: 'sg' } })
// logger: Logger<{ service: string; region: string }>

const child = logger.child({ requestId: 'r1' })
// child: Logger<{ service: string; region: string; requestId: string }>

const transport = (line: string, record: LogRecord<{ service: string }>) => {
  // record.service is typed string, no cast needed
}

redact(value, options?)

The standalone redactor for ad hoc use outside the logger. Useful before sending payloads to third party error reporters or analytics services.

import { redact } from 'autoredact'
sentry.captureException(error, { extra: redact(payload) })

RedactOptions

All fields are optional. Defaults cover the security baseline.

type RedactOptions = {
  // Add to the default phrase list. The recommended way to extend.
  // Default: []
  extraPhrases?: ReadonlyArray<ReadonlyArray<string>>

  // Replace the default phrase list entirely. Advanced.
  // Default: DEFAULT_PHRASES
  phrases?: ReadonlyArray<ReadonlyArray<string>>

  // Path allowlist. Values at these paths pass through untouched.
  // Default: []
  allow?: ReadonlyArray<string>

  // Replacement string for redacted values.
  // Default: '[REDACTED]'
  censor?: string

  // Whether to scan string values for credential shapes.
  // Default: true
  valueShapes?: boolean

  // Add to the default value shape regex set. Each entry is coerced to
  // carry the `g` flag automatically.
  // Default: []
  extraValuePatterns?: ReadonlyArray<RegExp>

  // Maximum object or array nesting depth before emitting a marker.
  // Default: 8
  maxDepth?: number

  // Maximum string length before truncation.
  // Default: 8192
  maxStringLen?: number

  // Optional callback invoked once per redaction event. Receives the path,
  // kind, matched substring, and reason. Useful for metrics and alarms.
  // Default: undefined
  onLeak?: (info: LeakInfo) => void
}

type LeakInfo = {
  path: string // 'user.api_key', 'env[3].token'
  kind: 'key' | 'value' // key match or string content match
  matched: string // the key name or the matched substring
  reason: 'phrase' | 'pattern' | 'luhn' // which detector fired
}

createLogger accepts the same callback at the top level so the logger forwards every redaction it performs:

const logger = createLogger({
  base: { service: 'api' },
  onLeak: (info) => metrics.increment('autoredact.leak', { reason: info.reason }),
})

A child logger inherits its parent's onLeak automatically.

Examples

Add a custom phrase

const logger = createLogger({
  redact: { extraPhrases: [['internal', 'id'], ['hmac']] },
})
logger.info({ internalId: 'abc', hmac: 'xyz' })
// internalId and hmac both redact

Allow a specific path

Useful when one specific field at a known path is safe to log even though its key name would otherwise match.

const logger = createLogger({
  redact: { allow: ['user.email', 'request.session.id'] },
})

Custom censor

const logger = createLogger({
  redact: { censor: '***' },
})

Custom value shape regex

const logger = createLogger({
  redact: {
    extraValuePatterns: [/internal-id-[a-f0-9]{16}/i],
  },
})

Custom transport

const logger = createLogger({
  transport: (line, record) => {
    fetch('https://logs.example.com/ingest', { method: 'POST', body: line })
  },
})

Observe what gets redacted

Wire onLeak to your metrics or alarm system to surface what would otherwise be silent. Useful both as a tripwire (a Stripe key fires a redaction, you want to know which call site put it there) and as a long term signal of where sanitisation is missing upstream.

const logger = createLogger({
  onLeak: (info) => {
    metrics.increment('autoredact.leak', { reason: info.reason, kind: info.kind })
    if (info.reason === 'pattern') {
      alarm.send(`credential shape redacted at ${info.path}`)
    }
  },
})

The same callback is available on redact() directly:

import { redact } from 'autoredact'

const safe = redact(payload, {
  onLeak: (info) => metrics.increment('autoredact.leak', { reason: info.reason }),
})

Standalone redactor

import { redact } from 'autoredact'

const safePayload = redact({ user, requestBody, headers })
sentry.captureException(error, { extra: safePayload })

CLI

autoredact-scan audits log files offline for leaks the application missed. Useful as a CI gate against historical files or as a periodic scan against a centralized log store.

npx autoredact-scan log.jsonl
npx autoredact-scan logs/*.jsonl
cat app.log | npx autoredact-scan
npx autoredact-scan --json log.jsonl > out.json
npx autoredact-scan --strict log.jsonl   # exit 1 if leaks found, for CI

Run autoredact-scan --help for the full flag list.

Resilience

The walker is hardened against the inputs that crash naive implementations. Logging never crashes the calling app:

  • Cycles emit [CIRCULAR] and recursion stops. Shared object references that are not cycles still walk normally.
  • Depth caps at maxDepth (default 8) and emits [TRUNCATED:DEPTH].
  • String length caps at maxStringLen (default 8192) with a [TRUNCATED] suffix appended after scrubbing, so a credential straddling the cap does not leak a partial.
  • Throwing getters substitute [GETTER_THREW] rather than crashing.
  • Walker errors degrade to a single _redact_error field on the record.
  • Transport errors log once via console.error and silence for the lifetime of the logger.

License

MIT License © 2026-present Cong Nguyen