Smart object redaction for JavaScript applications - safe AND fast!
Redact JS objects with the same API as fast-redact, but uses innovative selective cloning instead of mutating the original. This provides immutability guarantees with performance competitive to fast-redact for real-world usage patterns.
npm install @pinojs/redactconst pinoRedact = require('@pinojs/redact')
const redact = pinoRedact({
  paths: ['headers.cookie', 'headers.authorization', 'user.password']
})
const obj = {
  headers: {
    cookie: 'secret-session-token',
    authorization: 'Bearer abc123',
    'x-forwarded-for': '192.168.1.1'
  },
  user: {
    name: 'john',
    password: 'secret123'
  }
}
console.log(redact(obj))
// Output: {"headers":{"cookie":"[REDACTED]","authorization":"[REDACTED]","x-forwarded-for":"192.168.1.1"},"user":{"name":"john","password":"[REDACTED]"}}
// Original object is completely unchanged:
console.log(obj.headers.cookie) // 'secret-session-token'Creates a redaction function with the specified options.
- paths string[](required): An array of strings describing the nested location of a key in an object
- censor any(optional, default:'[REDACTED]'): The value to replace sensitive data with. Can be a static value or function.
- serialize Function|boolean(optional, default:JSON.stringify): Serialization function. Set tofalseto return the redacted object.
- remove boolean(optional, default:false): Remove redacted keys from serialized output
- strict boolean(optional, default:true): Throw on non-object values or pass through primitives
Supports the same path syntax as fast-redact:
- Dot notation: 'user.name','headers.cookie'
- Bracket notation: 'user["password"]','headers["X-Forwarded-For"]'
- Array indices: 'users[0].password','items[1].secret'
- Wildcards:
- Terminal: 'users.*.password'(redacts password for all users)
- Intermediate: '*.password'(redacts password at any level)
- Array wildcard: 'items.*'(redacts all array elements)
 
- Terminal: 
Custom censor value:
const redact = pinoRedact({
  paths: ['password'],
  censor: '***HIDDEN***'
})Dynamic censor function:
const redact = pinoRedact({
  paths: ['password'],
  censor: (value, path) => `REDACTED:${path}`
})Return object instead of JSON string:
const redact = pinoRedact({
  paths: ['secret'],
  serialize: false
})
const result = redact({ secret: 'hidden', public: 'data' })
console.log(result.secret) // '[REDACTED]'
console.log(result.public) // 'data'
// Restore original values
const restored = result.restore()
console.log(restored.secret) // 'hidden'Custom serialization:
const redact = pinoRedact({
  paths: ['password'],
  serialize: obj => JSON.stringify(obj, null, 2)
})Remove keys instead of redacting:
const redact = pinoRedact({
  paths: ['password', 'user.secret'],
  remove: true
})
const obj = { username: 'john', password: 'secret123', user: { name: 'Jane', secret: 'hidden' } }
console.log(redact(obj))
// Output: {"username":"john","user":{"name":"Jane"}}
// Note: 'password' and 'user.secret' are completely absent, not redactedWildcard patterns:
// Redact all properties in secrets object
const redact1 = pinoRedact({ paths: ['secrets.*'] })
// Redact password for any user
const redact2 = pinoRedact({ paths: ['users.*.password'] })
// Redact all items in an array
const redact3 = pinoRedact({ paths: ['items.*'] })
// Remove all secrets instead of redacting them
const redact4 = pinoRedact({ paths: ['secrets.*'], remove: true })- No mutation: Original objects are never modified
- Selective cloning: Only clones paths that need redaction, shares references for everything else
- Restore capability: Can restore original values when serialize: false
- Remove option: Full compatibility with fast-redact's remove: trueoption to completely omit keys from output
- All path patterns: Supports same syntax including wildcards, bracket notation, and array indices
- Censor functions: Dynamic censoring with path information passed as arrays
- Serialization: Custom serializers and serialize: falsemode
- Selective cloning: Analyzes redaction paths and only clones necessary object branches
- Reference sharing: Non-redacted properties maintain original object references
- Memory efficiency: Dramatically reduced memory usage for large objects with minimal redaction
- Setup-time optimization: Path analysis happens once during setup, not per redaction
- When immutability is critical
- When you need to preserve original objects
- When objects are shared across multiple contexts
- In functional programming environments
- When debugging and you need to compare before/after
- Large objects with selective redaction (now performance-competitive!)
- When memory efficiency with reference sharing is important
- When absolute maximum performance is critical
- In extremely high-throughput scenarios (>100,000 ops/sec)
- When you control the object lifecycle and mutation is acceptable
- Very small objects where setup overhead matters
@pinojs/redact uses selective cloning that provides good performance while maintaining immutability guarantees:
| Operation Type | @pinojs/redact | fast-redact | Performance Ratio | 
|---|---|---|---|
| Small objects | ~690ns | ~200ns | ~3.5x slower | 
| Large objects (minimal redaction) | ~18μs | ~17μs | ~same performance | 
| Large objects (wildcards) | ~48μs | ~37μs | ~1.3x slower | 
| No redaction (large objects) | ~18μs | ~17μs | ~same performance | 
@pinojs/redact is performance-competitive with fast-redact for large objects.
- Selective cloning approach: Only clones object paths that need redaction
- Reference sharing: Non-redacted properties share original object references
- Setup-time optimization: Path analysis happens once, not per redaction
- Memory efficiency: Dramatically reduced memory usage for typical use cases
Small Objects (~180 bytes):
- @pinojs/redact: 690ns per operation
- fast-redact: 200ns per operation
- Slight setup overhead for small objects
Large Objects (~18KB, minimal redaction):
- @pinojs/redact: 18μs per operation
- fast-redact: 17μs per operation
- Near-identical performance
Large Objects (~18KB, wildcard patterns):
- @pinojs/redact: 48μs per operation
- fast-redact: 37μs per operation
- Competitive performance for complex patterns
Memory Considerations:
- @pinojs/redact: Selective reference sharing (much lower memory usage than before)
- fast-redact: Mutates in-place (lowest memory usage)
- Large objects with few redacted paths now share most references
Choose fast-redact when:
- Absolute maximum performance is critical (>100,000 ops/sec)
- Working with very small objects frequently
- Mutation is acceptable and controlled
- Every microsecond counts
Choose @pinojs/redact when:
- Immutability is required (with competitive performance)
- Objects are shared across contexts
- Large objects with selective redaction
- Memory efficiency through reference sharing is important
- Safety and functionality are priorities
- Most production applications (performance gap is minimal)
Run benchmarks yourself:
npm run bench@pinojs/redact uses an innovative selective cloning approach that provides immutability guarantees while dramatically improving performance:
// Old approach: Deep clone entire object, then redact
const fullClone = deepClone(originalObject)  // Clone everything
redact(fullClone, paths)                     // Then redact specific paths// New approach: Analyze paths, clone only what's needed
const pathStructure = buildPathStructure(paths)  // One-time setup
const selectiveClone = cloneOnlyNeededPaths(obj, pathStructure)  // Smart cloning
redact(selectiveClone, paths)  // Redact pre-identified paths- Path Analysis: Pre-processes redaction paths into an efficient tree structure
- Selective Cloning: Only creates new objects for branches that contain redaction targets
- Reference Sharing: Non-redacted properties maintain exact same object references
- Setup Optimization: Path parsing happens once during redactor creation, not per redaction
const largeConfig = {
  database: { /* large config object */ },
  api: { /* another large config */ },
  secrets: { password: 'hidden', apiKey: 'secret' }
}
const redact = pinoRedact({ paths: ['secrets.password'] })
const result = redact(largeConfig)
// Only secrets object is cloned, database and api share original references
console.log(result.database === largeConfig.database)  // true - shared reference!
console.log(result.api === largeConfig.api)            // true - shared reference!
console.log(result.secrets === largeConfig.secrets)    // false - cloned for redactionThis approach provides immutability where it matters while sharing references where it's safe.
The remove: true option provides full compatibility with fast-redact's key removal functionality:
const redact = pinoRedact({
  paths: ['password', 'secrets.*', 'users.*.credentials'],
  remove: true
})
const data = {
  username: 'john',
  password: 'secret123',
  secrets: { apiKey: 'abc', token: 'xyz' },
  users: [
    { name: 'Alice', credentials: { password: 'pass1' } },
    { name: 'Bob', credentials: { password: 'pass2' } }
  ]
}
console.log(redact(data))
// Output: {"username":"john","secrets":{},"users":[{"name":"Alice"},{"name":"Bob"}]}| Option | Behavior | Output Example | 
|---|---|---|
| Default (redact) | Replaces values with censor | {"password":"[REDACTED]"} | 
| remove: true | Completely omits keys | {} | 
- Same output as fast-redact: Identical JSON output when using remove: true
- Wildcard support: Works with all wildcard patterns (*,users.*,items.*.secret)
- Array handling: Array items are set to undefined(omitted in JSON output)
- Nested paths: Supports deep removal (users.*.credentials.password)
- Serialize compatibility: Only works with JSON.stringifyserializer (like fast-redact)
# Run unit tests
npm test
# Run integration tests comparing with fast-redact
npm run test:integration
# Run all tests (unit + integration)
npm run test:all
# Run benchmarks
npm run bench- 16 unit tests: Core functionality and edge cases
- 16 integration tests: Output compatibility with fast-redact
- All major features: Paths, wildcards, serialization, custom censors
- Performance benchmarks: Direct comparison with fast-redact
MIT
Pull requests welcome! Please ensure all tests pass and add tests for new features.