Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Redactive

Fast, configurable sensitive-data redaction for JavaScript and TypeScript.

Redactive sanitizes structured objects and text before they reach logs, analytics systems, error trackers, third-party APIs, or AI providers. It supports compiled path rules, property matching, content detectors, custom replacement strategies, circular references, and immutable or in-place operation.

Installation

pnpm add redactive

Redactive is ESM-first and has no runtime dependencies.

Quick start

import { createRedactor } from 'redactive';

const redact = createRedactor({
  paths: ['password', 'headers.authorization'],
  detectors: ['email', 'jwt'],
});

const safe = redact({
  password: 'correct-horse-battery-staple',
  message: 'Contact user@example.com',
  headers: {
    authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.payload.signature',
  },
});

safe contains:

{
  password: '[REDACTED]',
  message: 'Contact [REDACTED]',
  headers: {
    authorization: '[REDACTED]',
  },
}

Configuration is validated and compiled by createRedactor. The returned redactor can be reused for every value that follows the same policy.

Why Redactive

Known paths are the most deterministic way to prevent sensitive values from leaving an application, but they do not cover credentials embedded in error messages, URLs, or user text. Redactive combines compiled structural matching with opt-in content detection while keeping the primary API synchronous and framework-independent.

  • Path and key rules are compiled once.
  • Content detectors are disabled unless selected.
  • Clone mode preserves the input and its shared-reference graph.
  • Mutation mode is available for controlled performance-sensitive paths.
  • Inspection metadata identifies what changed without retaining sensitive values.
  • The root package does not import Node.js built-ins.

Path redaction

Paths match own enumerable string-keyed properties. A plain path starts at the root. Use ** when the match must be recursive.

const redact = createRedactor({
  paths: [
    'password',
    'user.password',
    'users[*].email',
    'users[0].name',
    'headers["x-api-key"]',
    'metadata.*.secret',
    '**.accessToken',
  ],
});
Syntax Behavior
user.password Selects named properties.
users[0] Selects one array index.
users[*] Selects every array index.
metadata.* Selects one property segment.
**.password Selects password at any depth, including the root.
headers["x.api-key"] Selects a key containing dots or special characters.

Empty segments, unfinished brackets, nonnumeric indexes, partial wildcards, and standalone ** are rejected during createRedactor. When multiple path rules match the same value, the first configured rule wins.

import { mask } from 'redactive/replacements';

const redact = createRedactor({
  paths: [
    'password',
    {
      path: 'card.number',
      replacement: mask({ preserveEnd: 4 }),
    },
  ],
});

Key redaction

Key rules match property names independently of their structural location.

const redact = createRedactor({
  keys: [
    'password',
    /secret$/i,
    {
      test: (key) => key.startsWith('private'),
      replacement: null,
    },
  ],
});

String rules are exact and case-sensitive. Regular expressions provide explicit pattern and case behavior. Redactive compiles a private copy of each regular expression without stateful g or y flags, so user-owned lastIndex values are not changed. Custom matchers are trusted application code.

Content detection

Detectors examine string contents in primitive string values, error fields, and redact.text(). They do not run by default.

const redact = createRedactor({
  detectors: ['email', 'credit-card', 'jwt', 'private-key', 'connection-string'],
});

redact.text('Contact user@example.com');
// 'Contact [REDACTED]'

The following built-in names are available:

  • email
  • ipv4
  • ipv6
  • credit-card
  • jwt
  • bearer-token
  • basic-auth
  • private-key
  • connection-string
  • url-credentials
  • aws-access-key
  • github-token
  • openai-api-key
  • anthropic-api-key
  • stripe-secret-key

The credit-card detector normalizes supported spaces and hyphens, checks length, and requires a valid Luhn checksum. Token detectors require recognized prefixes and minimum lengths. These checks favor precision, but every content detector can produce false positives or false negatives.

Overlapping detections are resolved deterministically by confidence, detector specificity, match length, configured detector order, and start position. A high-confidence JWT detection therefore wins over a broader Bearer detection for the same token.

Individual detector objects are also exported from redactive/detectors for composition and testing.

Replacement strategies

The global replacement and structural rule replacements accept a string, null, or a trusted callback.

const redact = createRedactor({
  keys: ['password'],
  replacement: (_value, context) => `[REMOVED:${context.kind}]`,
});

remove(options?)

import { remove } from 'redactive/replacements';

remove();
remove({ arrays: 'compact' });

Object properties are removed. The default array behavior preserves indexes and writes undefined. The compact option removes matching indexes and shifts later entries. Runtime shape can therefore differ even though the generic output type remains the input type.

mask(options?)

import { mask } from 'redactive/replacements';

mask();
mask({ character: '*', preserveStart: 2, preserveEnd: 4 });

Masking operates on letters and numbers while retaining punctuation. This preserves credit-card separators during partial masking.

tokenize(options?)

import { tokenize } from 'redactive/replacements';

const token = tokenize({ prefix: 'EMAIL' });

One tokenizer maps repeated values to the same sequence token, such as [EMAIL_1]. It retains original values in an in-memory Map. Keep tokenizer lifetimes bounded and account for both the security and memory consequences.

hash(options?)

import { hash } from 'redactive/node';

const digest = hash({
  algorithm: 'sha256',
  salt: process.env.REDACTION_SALT,
  encoding: 'hex',
});

Synchronous cryptographic hashing is Node-specific and is isolated under redactive/node. Supported algorithms are sha256, sha384, and sha512. Supported encodings are hex, base64, and base64url. Use an application-managed salt when correlation across records is needed.

Inspection

redact.inspect(target) returns the sanitized value and safe metadata.

const result = redact.inspect({
  password: 'secret',
  message: 'Contact user@example.com',
});

result.value;
result.redactions;
result.truncated;

Each redaction includes its kind and structured path. Detector records also include the detector, confidence, and original half-open range. Records never contain the original value, a preview, captured groups, or a derived hash. The normal callable redactor does not build inspection records.

When a redactor is configured with mode: 'mutate', inspect reports the in-place operation. Use a clone-mode redactor when inspection must leave the target unchanged.

Clone and mutation modes

Clone mode is the default.

const output = redact(input);
output !== input;

Mutation is explicit through the method or configuration.

redact.mutate(input);

const mutate = createRedactor({
  mode: 'mutate',
  paths: ['password'],
});

Mutation is destructive. Frozen or non-writable matching properties can cause a TypeError. Replacement callbacks are trusted code, and a callback that throws can leave earlier mutations in place. Redactive does not claim transactional mutation.

Generated compilation, opt-in

Warning: Generated compilation uses new Function. Enable it only for trusted, application-owned path configuration and only in runtimes whose Content Security Policy permits dynamic code construction. It is disabled by default.

Generated compilation is an explicit performance option for a narrow structured mutation policy:

const redact = createRedactor({
  codegen: true,
  mode: 'mutate',
  paths: ['headers.authorization', 'user.password'],
  replacement: '[REDACTED]',
});

redact(logRecord); // mutates logRecord and returns the same reference

The generated plan supports exact property and array-index paths, mutate mode, and static string or null replacements. It emits one strict function for the entire policy. Path segments are serialized with JSON.stringify, while replacement values and descriptor-safe property helpers are supplied as factory arguments.

Generated compilation does not support clone mode, wildcards, recursive paths, key rules, custom matchers, detectors, replacement callbacks, removal callbacks, explicit limits, custom circular or container policies, or preservePrototype. Requesting any unsupported feature throws RedactiveCodegenUnsupportedError; it never silently falls back to another plan.

Generated mode rejects __proto__, prototype, and constructor at every path depth with RedactiveUnsafePathError. The default safe compiler may redact those names when they are genuine own data properties because it never evaluates paths and writes through property descriptors. This difference is intentional. If the runtime blocks new Function, redactor creation throws RedactiveCodegenUnavailableError and retains the runtime failure as cause.

Use the default compiler for untrusted or user-supplied policies, browsers with strict CSP, edge runtimes that prohibit dynamic code construction, and any configuration outside the generated subset. See the runnable generated compilation example.

Clone traversal uses own enumerable string properties and data descriptors. Enumerable accessors are copied as descriptors without invocation. Non-enumerable and symbol properties are ignored, except for the first-class Error fields described below. Suspicious keys such as __proto__ are defined as data properties rather than assigned. Clone outputs are mutable: copied properties are configurable, and copied data properties are writable. Proxy ownKeys and getOwnPropertyDescriptor traps necessarily execute, but property getters do not.

For configurations containing only static exact paths, Redactive uses selective copy-on-write cloning. It clones the root once, clones only ancestors that contain matches, and reuses untouched branches by reference. A single static * or [*] path uses the same policy. Dynamic callbacks, multiple overlapping wildcard rules, recursive wildcards, key rules, detectors, explicit limits, and non-default circular or container policies use the complete graph traversal engine.

By default:

  • Plain objects and arrays visited by the selected execution plan are cloned.
  • Null-prototype objects retain a null prototype.
  • Custom class instances become plain objects unless preservePrototype is true.
  • Errors are cloned and traversed through name, message, stack, cause, and enumerable custom properties without invoking accessors.
  • Dates, regular expressions, typed arrays, ArrayBuffers, URLs, Headers, Requests, Responses, Maps, and Sets are preserved by reference.
  • Functions and symbols are preserved by reference.

Map values and Set values can be traversed explicitly:

const redact = createRedactor({
  objects: { map: 'traverse', set: 'traverse' },
});

Map keys are preserved. String and numeric Map keys participate in path matching. Other Map keys use their insertion index for path matching. Set values use insertion indexes.

Circular references

Clone mode preserves shared identities among cloned ancestors and reconstructs cycles that pass through those ancestors. The complete traversal engine reconstructs the entire visited graph. Selective plans reuse untouched branches, including their internal references, as-is.

const input: Record<string, unknown> = {};
input.self = input;

const output = redact(input);
output.self === output; // true

When the same object is reachable through multiple matched paths, Redactive applies the union of those path rules to the shared clone. Immediate aliases in cloned ancestors point to the same sanitized value.

The circular option accepts:

  • preserve: reconstruct cycles and shared references. This is the default.
  • redact: replace actual back-references while retaining non-circular shared references.
  • throw: throw RedactiveCircularReferenceError at the first cycle.

Limits

Every redaction call enforces safeguards for untrusted data.

Option Type Default Behavior
limits.depth number 100 Maximum structured depth.
limits.nodes number 100000 Maximum visited values.
limits.stringLength number 1000000 Maximum string length scanned.
limits.redactions number 10000 Maximum applied redactions.
limits.matchesPerString number 1000 Maximum detector results per string.

onLimit controls the result:

  • redact: replace the limited value. This is the default.
  • preserve: return the limited value unchanged and set truncated during inspection.
  • throw: throw RedactiveLimitError without including the target value.

Presets

Presets return ordinary configuration fragments. They do not create or modify global state.

import { createRedactor } from 'redactive';
import { httpPreset } from 'redactive/presets';

const redact = createRedactor({
  ...httpPreset(),
  detectors: ['jwt', 'bearer-token'],
});
  • httpPreset() adds known HTTP credential keys, including authorization, cookies, API keys, passwords, and common token fields. It does not redact request bodies.
  • credentialsPreset() enables high-confidence authorization, credential, connection-string, private-key, and provider-token detectors.
  • personalDataPreset() enables email, IPv4, IPv6, and credit-card detectors. It is not a guarantee of regulatory compliance.

Custom detectors

Custom detectors are synchronous trusted application code.

import type { Detector } from 'redactive';

const accountIdDetector: Detector = {
  name: 'account-id',
  *detect(input) {
    const marker = 'acct_';
    let start = input.indexOf(marker);
    while (start !== -1) {
      yield {
        start,
        end: start + marker.length + 12,
        confidence: 'high',
      };
      start = input.indexOf(marker, start + 1);
    }
  },
};

const redact = createRedactor({
  detectors: [
    {
      detector: accountIdDetector,
      replacement: '[ACCOUNT]',
      minimumConfidence: 'medium',
    },
  ],
});

Ranges use UTF-16 offsets with an inclusive start and exclusive end. Empty, negative, reversed, non-integer, and out-of-bounds ranges throw RedactiveDetectorError. Detector exceptions are wrapped without copying their potentially sensitive messages.

HTTP usage

The redactive/http helpers work with Web-standard objects and never read request or response bodies.

import { createRedactor } from 'redactive';
import { redactRequest, redactResponse } from 'redactive/http';
import { httpPreset } from 'redactive/presets';

const redact = createRedactor({ ...httpPreset(), detectors: ['email'] });

const requestMetadata = redactRequest(request, redact);
const responseMetadata = redactResponse(response, redact);

redactRequest returns URL, method, and headers. redactResponse returns status, status text, and headers. redactHeaders accepts Headers directly. Passing a compiled redactor avoids recompiling a policy. If omitted, the helpers use httpPreset() for that call.

Framework examples

Complete examples are included and executed by the test suite:

  • Basic usage
  • Generated compilation, which uses trusted fixed paths in mutate mode.
  • Pino, which sanitizes before logging and does not patch Pino.
  • Hono, which redacts request metadata and caught errors.
  • Express, which creates middleware-local values without mutating req.
  • Next.js, which uses a Web-standard route-handler Request.
  • AI payload, which sanitizes text before constructing an external provider payload.

Redacting AI input can alter prompt semantics. Review detector and replacement policy for the application before sending sanitized text to a model provider.

Performance

Path expressions, key matchers, detector order, replacement strategies, and limits are compiled by createRedactor. Static exact paths use unrolled closure accessors for mutation and a selective copy-on-write tree for clone mode. A single static wildcard uses a bounded wildcard walker. More general configurations use the iterative graph engine and compiled path trie. Root strings bypass structured graph state and use precompiled regular expressions, detector prefilters, and linear string reconstruction. The default compiler never evaluates generated code. Only an explicit, compatible codegen: true configuration invokes new Function. Redactive does not clone through JSON serialization.

The Mitata benchmark separates exact mutation, selective clone, wildcard traversal, text detection, and compilation. It includes direct baselines and labeled comparisons with fast-redact, @pinojs/redact, redact-object, redact-pii, a recursive clone, and a JSON replacer only where their semantics are relevant.

pnpm benchmark
pnpm benchmark --smoke
pnpm benchmark --filter exact
pnpm benchmark:smoke

The methodology is documented in benchmarks/README.md. Reproducible results and machine details are recorded in benchmarks/RESULTS.md. Performance varies by runtime, hardware, input graph, and configured rules. Run the benchmark on representative data before making deployment decisions.

Security considerations

Redaction reduces accidental exposure. It does not guarantee compliance with GDPR, HIPAA, PCI DSS, SOC 2, or any other framework.

  • Structural path and key rules are more deterministic than content detection.
  • Content detection can produce false positives and false negatives.
  • Passwords embedded in arbitrary text cannot be identified reliably without structural context.
  • Tokenization retains original values in memory.
  • Custom detectors, key matchers, and replacement callbacks execute as trusted application code.
  • Generated compilation evaluates a function only after explicit opt-in and requires trusted paths.
  • Mutation can leave partial changes when a callback throws.
  • Limit preservation can retain an unscanned subtree or string.
  • Secrets still require appropriate secret-management, access-control, and retention systems.

Redactive never logs input, sends telemetry, performs network requests, or retains values unless an explicit tokenizer instance is selected. Error messages and inspection records omit target values. See SECURITY.md for vulnerability reporting.

API reference

Root export: redactive

createRedactor(options?): Redactor

Compiles options and returns a callable redactor with inspect, mutate, and text methods.

RedactorOptions

Option Type Default Behavior
paths readonly PathRule[] [] Compiled structural rules.
keys readonly KeyRule[] [] Exact, regular-expression, or custom key rules.
detectors readonly DetectorInput[] [] Opt-in content detectors.
replacement string | null | function '[REDACTED]' Global structural and detector fallback.
mode 'clone' | 'mutate' 'clone' Callable redactor operation.
codegen boolean false Opt-in exact static mutation code generation.
circular 'preserve' | 'redact' | 'throw' 'preserve' Circular-reference handling.
limits RedactionLimits See Limits Per-call safeguards.
onLimit 'preserve' | 'redact' | 'throw' 'redact' Limit behavior.
preservePrototype boolean false Custom clone prototype behavior.
objects TraversableObjectsOptions See clone behavior Map, Set, and Error traversal.

The root also exports all public configuration, detector, replacement, inspection, and result types, plus these typed errors:

  • RedactiveError, code ERR_REDACTIVE
  • RedactiveConfigurationError, code ERR_REDACTIVE_CONFIGURATION
  • RedactiveCodegenUnsupportedError, code ERR_REDACTIVE_CODEGEN_UNSUPPORTED
  • RedactiveCodegenUnavailableError, code ERR_REDACTIVE_CODEGEN_UNAVAILABLE
  • RedactiveUnsafePathError, code ERR_REDACTIVE_UNSAFE_PATH
  • RedactivePathSyntaxError, code ERR_REDACTIVE_PATH_SYNTAX
  • RedactiveDetectorError, code ERR_REDACTIVE_DETECTOR
  • RedactiveCircularReferenceError, code ERR_REDACTIVE_CIRCULAR
  • RedactiveLimitError, code ERR_REDACTIVE_LIMIT

Subpath exports

Export Public values
redactive/detectors Every built-in detector, builtInDetectors, and detector types.
redactive/replacements remove, mask, tokenize, and related types.
redactive/presets httpPreset, credentialsPreset, personalDataPreset.
redactive/http redactRequest, redactResponse, redactHeaders, and metadata types.
redactive/node hash and HashOptions.

Runtime support

Redactive tests Node.js 22, 24, and 26 in CI. The platform-neutral core is suitable for Bun, Deno-compatible bundlers, browsers, workers, and edge runtimes where the referenced Web-standard types exist. The root export has no Node.js built-in imports and is executed in an isolated browser-like module context during CI. Node-specific hashing is available only through redactive/node.

The package is ESM-first, tree-shakeable, side-effect-free, and includes source maps, declaration files, and declaration maps. CommonJS is not provided in version 1.

The portable default compiler does not require dynamic code evaluation. The optional generated compiler requires a runtime and deployment policy that permit new Function.

Comparison with related tools

Redactive is not intended to replace repository secret scanners, cloud DLP systems, or application-level access controls.

Use fast-redact when highly optimized known-path redaction for JSON logging is the only needed capability.

Use Redactive when an application needs a combination of structured path rules, content detection, inspection metadata, custom strategies, circular graph handling, and portable runtime support.

Limitations

  • Detection is pattern-based and cannot establish that every sensitive value was found.
  • High-entropy string detection is intentionally omitted to reduce false positives.
  • Maps and Sets are preserved unless traversal is selected explicitly.
  • Request and Response helpers do not inspect bodies.
  • General streaming JSON and text redaction are not included. Chunk-boundary correctness requires a dedicated streaming design.
  • Hashing is synchronous and Node-specific.
  • CommonJS is not included.
  • Generated compilation supports only trusted exact-path mutation policies and is unavailable under Content Security Policies that block dynamic code construction.

Contributing

See CONTRIBUTING.md for development commands, tests, benchmarks, changesets, and release setup.

License

MIT. See LICENSE.

About

Fast, configurable sensitive-data redaction for JavaScript and TypeScript.

Topics

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages