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.
pnpm add redactiveRedactive is ESM-first and has no runtime dependencies.
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.
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.
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 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.
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:
emailipv4ipv6credit-cardjwtbearer-tokenbasic-authprivate-keyconnection-stringurl-credentialsaws-access-keygithub-tokenopenai-api-keyanthropic-api-keystripe-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.
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}]`,
});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.
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.
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.
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.
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 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.
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 referenceThe 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
preservePrototypeis 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.
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; // trueWhen 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: throwRedactiveCircularReferenceErrorat the first cycle.
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 settruncatedduring inspection.throw: throwRedactiveLimitErrorwithout including the target value.
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 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.
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.
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.
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:smokeThe 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.
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.
Compiles options and returns a callable redactor with inspect, mutate, and text methods.
| 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, codeERR_REDACTIVERedactiveConfigurationError, codeERR_REDACTIVE_CONFIGURATIONRedactiveCodegenUnsupportedError, codeERR_REDACTIVE_CODEGEN_UNSUPPORTEDRedactiveCodegenUnavailableError, codeERR_REDACTIVE_CODEGEN_UNAVAILABLERedactiveUnsafePathError, codeERR_REDACTIVE_UNSAFE_PATHRedactivePathSyntaxError, codeERR_REDACTIVE_PATH_SYNTAXRedactiveDetectorError, codeERR_REDACTIVE_DETECTORRedactiveCircularReferenceError, codeERR_REDACTIVE_CIRCULARRedactiveLimitError, codeERR_REDACTIVE_LIMIT
| 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. |
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.
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.
- 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.
See CONTRIBUTING.md for development commands, tests, benchmarks, changesets, and release setup.
MIT. See LICENSE.