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

@hackylabs/deep-redact

v4.0.1

Published

Deeply redact sensitive data from objects, arrays and arbitrary strings (e.g. XML or raw cookies data) with a composable, function-first API.

Readme

Deep Redact

npm version GitHub license

@hackylabs/deep-redact is a rule-driven redaction library for Node.js and Deno. It lets you express what to redact — a key name, a path, a regex, or a substring — and the runtime locates and replaces it everywhere in the payload without requiring exhaustive path enumeration. Policies are compiled once at initialisation and reused across every request, making it suitable for high-throughput production use.

Libraries like fast-redact require an explicit path for every location where a sensitive field might appear (a.b.password, a.c.password, …). Deep Redact inverts this: a single keys: ['password'] rule targets every password field at any depth. It also adds capabilities that path-enumeration libraries cannot offer, such as partial string redaction (targeting a sensitive pattern inside a larger value) and wildcard path selectors for repeated structures.

ko-fi

Features

  • Depth-agnostic key rules — redact any field by name at any nesting depth; no path enumeration required
  • Exact and structured path targeting — pin redaction to a specific location when precision is needed
  • Regex property matching — match field names by regular expression
  • Substring targeting — redact a sensitive string that appears inside a larger value, not just whole-value replacement
  • Wildcard and exclusion path selectors — handle repeated structures at virtually any depth (['addresses.*', { ignore: 'country' }]) without listing every index
  • Structured and serialised output — return a live object graph or a guaranteed-JSON-safe string (serialise: true)
  • Built-in and custom transformers — override how specific runtime types are represented in serialised output
  • Built-in security — prevent prototype pollution and DoS by memory exhaustion
  • Console adapter — optional console.* redaction via an explicit adapter

Contributor Baseline

Installation

npm install @hackylabs/deep-redact
pnpm add @hackylabs/deep-redact
yarn add @hackylabs/deep-redact
bun add @hackylabs/deep-redact

Deno — add the package to your import map (deno.json):

{
  "imports": {
    "@hackylabs/deep-redact": "npm:@hackylabs/[email protected]"
  }
}

Quick Start

import { deepRedact } from '@hackylabs/deep-redact'

const redact = deepRedact({ keys: ['password', 'token'] })

redact({ user: { password: 'secret', safe: 'keep this' }, token: 'abc123', id: 1 })
// { user: { password: '[REDACTED]', safe: 'keep this' }, token: '[REDACTED]', id: 1 }

Benchmarks

Generated from committed artefacts in test/artefacts/benchmarks/speed/. Node.js 24 LTS, Apple M-series. Steady-state after 10,000 warmup iterations.

TLDR Headline

If raw throughput is your primary constraint and you can accept exhaustive path enumeration, use fast-redact instead. It is purpose-built for maximum JSON-string output speed and will outperform any rule-driven library in that narrow scenario. Use Deep Redact when you need depth-agnostic targeting, wildcard paths, substring redaction, or built-in security guarantees.

If you choose fast-redact instead, be aware of the trade-offs:

  • No depth-agnostic key rules — every sensitive path must be enumerated explicitly
  • No double-wildcard (**) path selectors — you cannot target records.**.email without listing every depth
  • No substring targeting — whole-value replacement only; partial string redaction is not supported
  • No serialisation-safe transformer pipeline — no built-in handling for BigInt, Date, Map, Set, Error, RegExp, or URL values
  • No prototype pollution protection — no guard against __proto__ and constructor.prototype injection
  • No configurable depth or node limits — no protection against DoS via deeply nested or excessively large payloads

Structured output (serialise: false)

v4, v3, and fast-redact all return a plain JavaScript object.

Speed comparison — structured output

† fast-redact is a third-party library, not a deep-redact version. It is shown as a throughput reference; its feature set and guarantees differ from deep-redact's, so it is not a like-for-like comparison.

v4 is ~17× faster than v3 on path-based workloads and ~10× faster on wildcard workloads.

Serialised output (serialise: true)

All four solutions return a JSON string.

Speed comparison — serialised output

† fast-redact is a third-party library; json-stringify-regex is a naive native approach (JSON.stringify(value).replace(pattern, replacement)), not a library. Neither performs deep-redact's structured redaction or offers its guarantees, so both are shown as throughput references rather than like-for-like comparisons.

v4 remains faster than v3 in serialised mode. fast-redact and json-stringify-regex have a throughput advantage because their output path is oriented entirely toward string production.

Against deep-redact v2 the serialised picture is mixed: v4 is roughly at parity on path-based workloads but slower on breadth-heavy (wildcard) ones. That gap is the cost of safety v2 does not provide — under serialise: true, v4 runs the type transformers that make BigInt, Date, Map, Set, Error, RegExp, and URL values JSON-safe, and it detects and neutralises circular references instead of throwing on them. (Node and depth budgeting via maxNodes/maxDepth is opt-in and unlimited by default, so it adds nothing unless you enable it.)

These safety passes are intrinsic to serialise: true and cannot be switched off individually. If you do not need those guarantees and want to match v2's throughput (or better), set serialise: false and run your own JSON.stringify: the structured-output path skips the transformer and circular-reference passes entirely. This restores v2's trade-offs too — your JSON.stringify will throw on BigInt and circular references, Map/Set serialise as {}, and undefined is dropped.

Full speed and resource benchmark results: docs/benchmarks/speed-results.md and docs/benchmarks/resource-results.md.

Configuration

deepRedact(options) accepts a single options object. All options are optional.

Top-level options

| Option | Type | Default | Required | Description | |--------|------|---------|----------|-------------| | keys | KeySelector[] | [] | No | Redact any field matching a listed key, at any depth | | paths | PathEntry[] | [] | No | Redact fields at specific paths; supports exact strings, structured paths, wildcards, and exclusion selectors | | stringTests | StringTest[] | [] | No | Redact matching patterns inside string values | | censor | string \| function | '[REDACTED]' | No | Replacement value, or a function (value, context) => replacement | | serialise | boolean \| function | false | No | true returns a JSON-safe string; a function receives the safe graph and may return any string | | remove | boolean | false | No | Remove matched keys from the output instead of replacing their values | | retainStructure | boolean | false | No | Keep descendant nodes traversable for lower-precedence rules when a parent is matched | | replaceStringByLength | boolean | false | No | Repeat the censor string to match the character length of the redacted value | | types | ValueTypeName[] | ['string'] | No | typeof categories eligible for redaction; a matched key whose value's type is not listed is left untouched | | fuzzyKeyMatch | boolean | false | No | Match when the configured key is a substring of the field name (e.g. 'pass' matches 'password') | | caseSensitiveKeyMatch | boolean | true | No | When false, normalises key names (strips non-word characters, lowercases) before comparing | | maxDepth | number | unlimited | No | Stop traversal beyond this nesting depth; guards against deeply nested payload DoS | | maxNodes | number | unlimited | No | Stop traversal after this many nodes; guards against excessively large payload DoS | | transformers | TransformersOption | built-in defaults | No | Override how runtime types are represented in serialised output | | diagnostics | DiagnosticsOptions | — | No | Receive a structured event when a value is degraded to [UNSUPPORTED] during serialisation |

ValueTypeName is one of: 'string', 'number', 'bigint', 'boolean', 'object', 'function', 'symbol', 'undefined'.

KeySelector

Each entry in keys may be a plain string, a RegExp, or a KeyRule object for per-key overrides.

| Field | Type | Default | Required | Description | |-------|------|---------|----------|-------------| | key | string \| RegExp | — | Yes | The key to match | | censor | string \| function | top-level censor | No | Override the censor for this key only | | remove | boolean | top-level remove | No | Override remove for this key only | | retainStructure | boolean | top-level retainStructure | No | Override retainStructure for this key only | | replaceStringByLength | boolean | top-level replaceStringByLength | No | Override replaceStringByLength for this key only | | fuzzyKeyMatch | boolean | top-level fuzzyKeyMatch | No | Override fuzzyKeyMatch for this key only | | caseSensitiveKeyMatch | boolean | top-level caseSensitiveKeyMatch | No | Override caseSensitiveKeyMatch for this key only |

PathEntry

Each entry in paths may be a plain dot-notation string (e.g. 'user.profile.ssn'), a structured path array, or a PathRule object for per-path overrides.

Structured path arrays may contain:

| Segment type | Example | Matches | |---|---|---| | string or number | 'password' | Exact key or index | | RegExp | /^pass/i | Keys matching the regex | | { any: true } | — | Any single key (*) | | { anyDepth: true } | — | Zero or more keys at any depth (**) | | { ignore: string \| number \| RegExp } | { ignore: 'country' } | Excludes a key from a surrounding wildcard |

| Field | Type | Default | Required | Description | |-------|------|---------|----------|-------------| | path | string \| PathSegments[] | — | Yes | The path to match | | censor | string \| function | top-level censor | No | Override the censor for this path only | | remove | boolean | top-level remove | No | Override remove for this path only | | retainStructure | boolean | top-level retainStructure | No | Override retainStructure for this path only | | replaceStringByLength | boolean | top-level replaceStringByLength | No | Override replaceStringByLength for this path only |

StringTest

Each entry in stringTests may be a bare RegExp (whole-value replacement with censor) or a SubstringRule for partial replacement.

| Field | Type | Required | Description | |-------|------|----------|-------------| | pattern | RegExp | Yes | Regular expression tested against string values | | replacer | (value: string, pattern: RegExp) => string | Yes | Returns the redacted string; called only when pattern matches |

TransformersOption

Transformers control how non-JSON-safe runtime types are represented when serialise: true. Each transformer is a function (value: unknown) => unknown that either returns a transformed value or returns the value unchanged to pass to the next transformer in the chain.

| Field | Type | Description | |-------|------|-------------| | byType.bigint | Transformer[] | Applied to BigInt values (does not consult byType.object) | | byType.object | Transformer[] | Applied first for built-in constructor types and unknown object types | | byConstructor.Date | Transformer[] | Applied to Date instances | | byConstructor.Error | Transformer[] | Applied to Error instances | | byConstructor.Map | Transformer[] | Applied to Map instances | | byConstructor.RegExp | Transformer[] | Applied to RegExp instances | | byConstructor.Set | Transformer[] | Applied to Set instances | | byConstructor.URL | Transformer[] | Applied to URL instances | | byConstructor.custom | CustomConstructorTransformerRegistration[] | Custom types matched by instanceof in declaration order | | fallback | Transformer[] | Applied when no earlier transformer returns a changed value |

The built-in transformers produce deterministic marker objects (e.g. { _transformer: 'date', datetime: '<ISO string>' }). See docs/architecture/serialise-output.md for the full dispatch order and marker shapes.

DiagnosticsOptions

| Field | Type | Description | |-------|------|-------------| | sink | (event: DiagnosticEvent) => void | Called with a structured event when a value is degraded to '[UNSUPPORTED]' during serialisation |

Public API

import { createRedactor, deepRedact } from '@hackylabs/deep-redact'

// deepRedact and createRedactor are the same factory under different names
const redact = deepRedact({ keys: ['secret'] })
const same = createRedactor({ keys: ['secret'] })

Documentation

Worked examples

Migration guides

Architecture and design

Platform and security teams

  • Standardisation guide — supported capabilities, targeting semantics, verification evidence, and adoption decision scope

Scripts

  • pnpm run build
  • pnpm run lint
  • pnpm run test
  • pnpm run generate-exports
  • pnpm run generate-readme
  • pnpm run verify-generated-files
  • pnpm run bench
  • pnpm run bench:generate-charts