228 lines
7.5 KiB
JavaScript
228 lines
7.5 KiB
JavaScript
'use strict'
|
|
|
|
const { test } = require('node:test')
|
|
const { strict: assert } = require('node:assert')
|
|
const slowRedact = require('../index.js')
|
|
|
|
// Tests for Issue #2319: @pinojs/redact fails to redact patterns with 3+ consecutive wildcards
|
|
test('three consecutive wildcards: *.*.*.password (4 levels deep)', () => {
|
|
const obj = {
|
|
simple: { password: 'secret-2-levels' },
|
|
user: { auth: { password: 'secret-3-levels' } },
|
|
nested: { deep: { auth: { password: 'secret-4-levels' } } }
|
|
}
|
|
|
|
const redact = slowRedact({
|
|
paths: ['*.*.*.password']
|
|
})
|
|
const result = redact(obj)
|
|
const parsed = JSON.parse(result)
|
|
|
|
// Only the 4-level deep password should be redacted
|
|
assert.strictEqual(parsed.simple.password, 'secret-2-levels', '2-level password should NOT be redacted')
|
|
assert.strictEqual(parsed.user.auth.password, 'secret-3-levels', '3-level password should NOT be redacted')
|
|
assert.strictEqual(parsed.nested.deep.auth.password, '[REDACTED]', '4-level password SHOULD be redacted')
|
|
})
|
|
|
|
test('four consecutive wildcards: *.*.*.*.password (5 levels deep)', () => {
|
|
const obj = {
|
|
simple: { password: 'secret-2-levels' },
|
|
user: { auth: { password: 'secret-3-levels' } },
|
|
nested: { deep: { auth: { password: 'secret-4-levels' } } },
|
|
config: { user: { auth: { settings: { password: 'secret-5-levels' } } } }
|
|
}
|
|
|
|
const redact = slowRedact({
|
|
paths: ['*.*.*.*.password']
|
|
})
|
|
const result = redact(obj)
|
|
const parsed = JSON.parse(result)
|
|
|
|
// Only the 5-level deep password should be redacted
|
|
assert.strictEqual(parsed.simple.password, 'secret-2-levels', '2-level password should NOT be redacted')
|
|
assert.strictEqual(parsed.user.auth.password, 'secret-3-levels', '3-level password should NOT be redacted')
|
|
assert.strictEqual(parsed.nested.deep.auth.password, 'secret-4-levels', '4-level password should NOT be redacted')
|
|
assert.strictEqual(parsed.config.user.auth.settings.password, '[REDACTED]', '5-level password SHOULD be redacted')
|
|
})
|
|
|
|
test('five consecutive wildcards: *.*.*.*.*.password (6 levels deep)', () => {
|
|
const obj = {
|
|
simple: { password: 'secret-2-levels' },
|
|
user: { auth: { password: 'secret-3-levels' } },
|
|
nested: { deep: { auth: { password: 'secret-4-levels' } } },
|
|
config: { user: { auth: { settings: { password: 'secret-5-levels' } } } },
|
|
data: {
|
|
reqConfig: {
|
|
data: {
|
|
credentials: {
|
|
settings: {
|
|
password: 'secret-6-levels'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const redact = slowRedact({
|
|
paths: ['*.*.*.*.*.password']
|
|
})
|
|
const result = redact(obj)
|
|
const parsed = JSON.parse(result)
|
|
|
|
// Only the 6-level deep password should be redacted
|
|
assert.strictEqual(parsed.simple.password, 'secret-2-levels', '2-level password should NOT be redacted')
|
|
assert.strictEqual(parsed.user.auth.password, 'secret-3-levels', '3-level password should NOT be redacted')
|
|
assert.strictEqual(parsed.nested.deep.auth.password, 'secret-4-levels', '4-level password should NOT be redacted')
|
|
assert.strictEqual(parsed.config.user.auth.settings.password, 'secret-5-levels', '5-level password should NOT be redacted')
|
|
assert.strictEqual(parsed.data.reqConfig.data.credentials.settings.password, '[REDACTED]', '6-level password SHOULD be redacted')
|
|
})
|
|
|
|
test('three wildcards with censor function receives correct values', () => {
|
|
const obj = {
|
|
nested: { deep: { auth: { password: 'secret-value' } } }
|
|
}
|
|
|
|
const censorCalls = []
|
|
const redact = slowRedact({
|
|
paths: ['*.*.*.password'],
|
|
censor: (value, path) => {
|
|
censorCalls.push({ value, path: [...path] })
|
|
return '[REDACTED]'
|
|
}
|
|
})
|
|
|
|
const result = redact(obj)
|
|
const parsed = JSON.parse(result)
|
|
|
|
// Should have been called exactly once with the correct value
|
|
assert.strictEqual(censorCalls.length, 1, 'censor should be called once')
|
|
assert.strictEqual(censorCalls[0].value, 'secret-value', 'censor should receive the actual value')
|
|
assert.deepStrictEqual(censorCalls[0].path, ['nested', 'deep', 'auth', 'password'], 'censor should receive correct path')
|
|
assert.strictEqual(parsed.nested.deep.auth.password, '[REDACTED]')
|
|
})
|
|
|
|
test('three wildcards with multiple matches', () => {
|
|
const obj = {
|
|
api1: { v1: { auth: { token: 'token1' } } },
|
|
api2: { v2: { auth: { token: 'token2' } } },
|
|
api3: { v1: { auth: { token: 'token3' } } }
|
|
}
|
|
|
|
const redact = slowRedact({
|
|
paths: ['*.*.*.token']
|
|
})
|
|
const result = redact(obj)
|
|
const parsed = JSON.parse(result)
|
|
|
|
// All three tokens should be redacted
|
|
assert.strictEqual(parsed.api1.v1.auth.token, '[REDACTED]')
|
|
assert.strictEqual(parsed.api2.v2.auth.token, '[REDACTED]')
|
|
assert.strictEqual(parsed.api3.v1.auth.token, '[REDACTED]')
|
|
})
|
|
|
|
test('three wildcards with remove option', () => {
|
|
const obj = {
|
|
nested: { deep: { auth: { password: 'secret', username: 'admin' } } }
|
|
}
|
|
|
|
const redact = slowRedact({
|
|
paths: ['*.*.*.password'],
|
|
remove: true
|
|
})
|
|
const result = redact(obj)
|
|
const parsed = JSON.parse(result)
|
|
|
|
// Password should be removed entirely
|
|
assert.strictEqual('password' in parsed.nested.deep.auth, false, 'password key should be removed')
|
|
assert.strictEqual(parsed.nested.deep.auth.username, 'admin', 'username should remain')
|
|
})
|
|
|
|
test('mixed: two and three wildcards in same redactor', () => {
|
|
const obj = {
|
|
user: { auth: { password: 'secret-3-levels' } },
|
|
config: { deep: { auth: { password: 'secret-4-levels' } } }
|
|
}
|
|
|
|
const redact = slowRedact({
|
|
paths: ['*.*.password', '*.*.*.password']
|
|
})
|
|
const result = redact(obj)
|
|
const parsed = JSON.parse(result)
|
|
|
|
// Both should be redacted
|
|
assert.strictEqual(parsed.user.auth.password, '[REDACTED]', '3-level should be redacted by *.*.password')
|
|
assert.strictEqual(parsed.config.deep.auth.password, '[REDACTED]', '4-level should be redacted by *.*.*.password')
|
|
})
|
|
|
|
test('three wildcards should not call censor for non-existent paths', () => {
|
|
const obj = {
|
|
shallow: { data: 'value' },
|
|
nested: { deep: { auth: { password: 'secret' } } }
|
|
}
|
|
|
|
let censorCallCount = 0
|
|
const redact = slowRedact({
|
|
paths: ['*.*.*.password'],
|
|
censor: (value, path) => {
|
|
censorCallCount++
|
|
return '[REDACTED]'
|
|
}
|
|
})
|
|
|
|
redact(obj)
|
|
|
|
// Should only be called once for the path that exists
|
|
assert.strictEqual(censorCallCount, 1, 'censor should only be called for existing paths')
|
|
})
|
|
|
|
test('three wildcards with arrays', () => {
|
|
const obj = {
|
|
users: [
|
|
{ auth: { password: 'secret1' } },
|
|
{ auth: { password: 'secret2' } }
|
|
]
|
|
}
|
|
|
|
const redact = slowRedact({
|
|
paths: ['*.*.*.password']
|
|
})
|
|
const result = redact(obj)
|
|
const parsed = JSON.parse(result)
|
|
|
|
// Both passwords should be redacted (users[0].auth.password is 4 levels)
|
|
assert.strictEqual(parsed.users[0].auth.password, '[REDACTED]')
|
|
assert.strictEqual(parsed.users[1].auth.password, '[REDACTED]')
|
|
})
|
|
|
|
test('four wildcards with authorization header (real-world case)', () => {
|
|
const obj = {
|
|
requests: {
|
|
api1: {
|
|
config: {
|
|
headers: {
|
|
authorization: 'Bearer secret-token'
|
|
}
|
|
}
|
|
},
|
|
api2: {
|
|
config: {
|
|
headers: {
|
|
authorization: 'Bearer another-token'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const redact = slowRedact({
|
|
paths: ['*.*.*.*.authorization']
|
|
})
|
|
const result = redact(obj)
|
|
const parsed = JSON.parse(result)
|
|
|
|
// Both authorization headers should be redacted
|
|
assert.strictEqual(parsed.requests.api1.config.headers.authorization, '[REDACTED]')
|
|
assert.strictEqual(parsed.requests.api2.config.headers.authorization, '[REDACTED]')
|
|
})
|