37
node_modules/fast-redact/.github/workflows/ci.yml
generated
vendored
37
node_modules/fast-redact/.github/workflows/ci.yml
generated
vendored
@@ -1,37 +0,0 @@
|
||||
name: ci
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
# This allows a subsequently queued workflow run to interrupt previous runs
|
||||
concurrency:
|
||||
group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [6.x, 8.x, 10.x, 12.x, 14.x, 16.x, 18.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
npm run test
|
||||
21
node_modules/fast-redact/LICENSE
generated
vendored
21
node_modules/fast-redact/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019-2020 David Mark Clements
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
223
node_modules/fast-redact/benchmark/index.js
generated
vendored
223
node_modules/fast-redact/benchmark/index.js
generated
vendored
@@ -1,223 +0,0 @@
|
||||
'use strict'
|
||||
const bench = require('fastbench')
|
||||
const fastRedact = require('..')
|
||||
|
||||
const censorFn = (v) => v + '.'
|
||||
const censorFnWithPath = (v, p) => v + '.' + p
|
||||
|
||||
const noir = require('pino-noir')(['aa.b.c'])
|
||||
const redactNoSerialize = fastRedact({ paths: ['ab.b.c'], serialize: false })
|
||||
const redactNoSerializeRestore = fastRedact({ paths: ['ac.b.c'], serialize: false })
|
||||
const noirWild = require('pino-noir')(['ad.b.*'])
|
||||
const redactWildNoSerialize = fastRedact({ paths: ['ae.b.*'], serialize: false })
|
||||
const redactWildNoSerializeRestore = fastRedact({ paths: ['af.b.*'], serialize: false })
|
||||
const redactIntermediateWildNoSerialize = fastRedact({ paths: ['ag.*.c'], serialize: false })
|
||||
const redactIntermediateWildNoSerializeRestore = fastRedact({ paths: ['ah.*.c'], serialize: false })
|
||||
const noirJSONSerialize = require('pino-noir')(['aj.b.c']) // `ai` used in pure JSON test.
|
||||
const redact = fastRedact({ paths: ['ak.b.c'] })
|
||||
const noirWildJSONSerialize = require('pino-noir')(['al.b.c'])
|
||||
const redactWild = fastRedact({ paths: ['am.b.*'] })
|
||||
const redactIntermediateWild = fastRedact({ paths: ['an.*.c'] })
|
||||
const redactIntermediateWildMatchWildOutcome = fastRedact({ paths: ['ao.*.c', 'ao.*.b', 'ao.*.a'] })
|
||||
const redactStaticMatchWildOutcome = fastRedact({ paths: ['ap.b.c', 'ap.d.a', 'ap.d.b', 'ap.d.c'] })
|
||||
const noirCensorFunction = require('pino-noir')(['aq.b.*'], censorFn)
|
||||
const redactCensorFunction = fastRedact({ paths: ['ar.b.*'], censor: censorFn, serialize: false })
|
||||
const redactIntermediateWildCensorFunction = fastRedact({ paths: ['as.*.c'], censor: censorFn, serialize: false })
|
||||
const redactCensorFunctionWithPath = fastRedact({ paths: ['at.d.b'], censor: censorFn, serialize: false })
|
||||
const redactWildCensorFunctionWithPath = fastRedact({ paths: ['au.d.*'], censor: censorFnWithPath, serialize: false })
|
||||
const redactIntermediateWildCensorFunctionWithPath = fastRedact({ paths: ['av.*.c'], censorFnWithPath, serialize: false })
|
||||
const redactMultiWild = fastRedact({ paths: ['aw.*.*'] })
|
||||
const redactMultiWildCensorFunction = fastRedact({ paths: ['ax.*.*'], censor: censorFn, serialize: false })
|
||||
|
||||
const getObj = (outerKey) => ({
|
||||
[outerKey]: {
|
||||
b: {
|
||||
c: 's'
|
||||
},
|
||||
d: {
|
||||
a: 's',
|
||||
b: 's',
|
||||
c: 's'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const max = 500
|
||||
|
||||
var run = bench([
|
||||
function benchNoirV2 (cb) {
|
||||
const obj = getObj('aa')
|
||||
for (var i = 0; i < max; i++) {
|
||||
noir.aa(obj.aa)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedact (cb) {
|
||||
const obj = getObj('ab')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactNoSerialize(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactRestore (cb) {
|
||||
const obj = getObj('ac')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactNoSerializeRestore(obj)
|
||||
redactNoSerializeRestore.restore(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchNoirV2Wild (cb) {
|
||||
const obj = getObj('ad')
|
||||
for (var i = 0; i < max; i++) {
|
||||
noirWild.ad(obj.ad)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactWild (cb) {
|
||||
const obj = getObj('ae')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactWildNoSerialize(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactWildRestore (cb) {
|
||||
const obj = getObj('af')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactWildNoSerializeRestore(obj)
|
||||
redactWildNoSerializeRestore.restore(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactIntermediateWild (cb) {
|
||||
const obj = getObj('ag')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactIntermediateWildNoSerialize(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactIntermediateWildRestore (cb) {
|
||||
const obj = getObj('ah')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactIntermediateWildNoSerializeRestore(obj)
|
||||
redactIntermediateWildNoSerializeRestore.restore(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchJSONStringify (cb) {
|
||||
const obj = getObj('ai')
|
||||
for (var i = 0; i < max; i++) {
|
||||
JSON.stringify(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchNoirV2Serialize (cb) {
|
||||
const obj = getObj('aj')
|
||||
for (var i = 0; i < max; i++) {
|
||||
noirJSONSerialize.aj(obj.aj)
|
||||
JSON.stringify(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactSerialize (cb) {
|
||||
const obj = getObj('ak')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redact(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchNoirV2WildSerialize (cb) {
|
||||
const obj = getObj('al')
|
||||
for (var i = 0; i < max; i++) {
|
||||
noirWildJSONSerialize.al(obj.al)
|
||||
JSON.stringify(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactWildSerialize (cb) {
|
||||
const obj = getObj('am')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactWild(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactIntermediateWildSerialize (cb) {
|
||||
const obj = getObj('an')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactIntermediateWild(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactIntermediateWildMatchWildOutcomeSerialize (cb) {
|
||||
const obj = getObj('ao')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactIntermediateWildMatchWildOutcome(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactStaticMatchWildOutcomeSerialize (cb) {
|
||||
const obj = getObj('ap')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactStaticMatchWildOutcome(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchNoirV2CensorFunction (cb) {
|
||||
const obj = getObj('aq')
|
||||
for (var i = 0; i < max; i++) {
|
||||
noirCensorFunction.aq(obj.aq)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactCensorFunction (cb) {
|
||||
const obj = getObj('ar')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactCensorFunction(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactCensorFunctionIntermediateWild (cb) {
|
||||
const obj = getObj('as')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactIntermediateWildCensorFunction(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactCensorFunctionWithPath (cb) {
|
||||
const obj = getObj('at')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactCensorFunctionWithPath(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactWildCensorFunctionWithPath (cb) {
|
||||
const obj = getObj('au')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactWildCensorFunctionWithPath(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactIntermediateWildCensorFunctionWithPath (cb) {
|
||||
const obj = getObj('av')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactIntermediateWildCensorFunctionWithPath(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactMultiWild (cb) {
|
||||
const obj = getObj('aw')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactMultiWild(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
},
|
||||
function benchFastRedactMultiWildCensorFunction (cb) {
|
||||
const obj = getObj('ax')
|
||||
for (var i = 0; i < max; i++) {
|
||||
redactMultiWildCensorFunction(obj)
|
||||
}
|
||||
setImmediate(cb)
|
||||
}
|
||||
], 500)
|
||||
|
||||
run(run)
|
||||
14
node_modules/fast-redact/example/default-usage.js
generated
vendored
14
node_modules/fast-redact/example/default-usage.js
generated
vendored
@@ -1,14 +0,0 @@
|
||||
'use strict'
|
||||
const fastRedact = require('..')
|
||||
const fauxRequest = {
|
||||
headers: {
|
||||
host: 'http://example.com',
|
||||
cookie: `oh oh we don't want this exposed in logs in etc.`,
|
||||
referer: `if we're cool maybe we'll even redact this`
|
||||
}
|
||||
}
|
||||
const redact = fastRedact({
|
||||
paths: ['headers.cookie', 'headers.referer']
|
||||
})
|
||||
|
||||
console.log(redact(fauxRequest))
|
||||
11
node_modules/fast-redact/example/intermediate-wildcard-array.js
generated
vendored
11
node_modules/fast-redact/example/intermediate-wildcard-array.js
generated
vendored
@@ -1,11 +0,0 @@
|
||||
'use strict'
|
||||
const fastRedact = require('..')
|
||||
const redact = fastRedact({ paths: ['a[*].c.d'] })
|
||||
const obj = {
|
||||
a: [
|
||||
{ c: { d: 'hide me', e: 'leave me be' } },
|
||||
{ c: { d: 'and me', f: 'I want to live' } },
|
||||
{ c: { d: 'and also I', g: 'I want to run in a stream' } }
|
||||
]
|
||||
}
|
||||
console.log(redact(obj))
|
||||
11
node_modules/fast-redact/example/multi-wildcard-array-depth.js
generated
vendored
11
node_modules/fast-redact/example/multi-wildcard-array-depth.js
generated
vendored
@@ -1,11 +0,0 @@
|
||||
'use strict'
|
||||
const fastRedact = require('..')
|
||||
const redact = fastRedact({ paths: ['a[*].c.d[*].i'] })
|
||||
const obj = {
|
||||
a: [
|
||||
{ c: { d: [ { i: 'redact me', j: 'not me' } ], e: 'leave me be' } },
|
||||
{ c: { d: [ { i: 'redact me too', j: 'not me' }, { i: 'redact me too', j: 'not me' } ], f: 'I want to live' } },
|
||||
{ c: { d: [ { i: 'redact me 3', j: 'not me' } ], g: 'I want to run in a stream' } }
|
||||
]
|
||||
}
|
||||
console.log(redact(obj))
|
||||
11
node_modules/fast-redact/example/multi-wildcard-array-end.js
generated
vendored
11
node_modules/fast-redact/example/multi-wildcard-array-end.js
generated
vendored
@@ -1,11 +0,0 @@
|
||||
'use strict'
|
||||
const fastRedact = require('..')
|
||||
const redact = fastRedact({ paths: ['a[*].c.d[*]'] })
|
||||
const obj = {
|
||||
a: [
|
||||
{ c: { d: ['hide me', '2'], e: 'leave me be' } },
|
||||
{ c: { d: ['and me'], f: 'I want to live' } },
|
||||
{ c: { d: ['and also I'], g: 'I want to run in a stream' } }
|
||||
]
|
||||
}
|
||||
console.log(redact(obj))
|
||||
11
node_modules/fast-redact/example/multi-wildcard-array.js
generated
vendored
11
node_modules/fast-redact/example/multi-wildcard-array.js
generated
vendored
@@ -1,11 +0,0 @@
|
||||
'use strict'
|
||||
const fastRedact = require('..')
|
||||
const redact = fastRedact({ paths: ['a[*].c[*].d'] })
|
||||
const obj = {
|
||||
a: [
|
||||
{ c: [{ d: 'hide me', e: 'leave me be' }, { d: 'hide me too', e: 'leave me be' }, { d: 'hide me 3', e: 'leave me be' }] },
|
||||
{ c: [{ d: 'and me', f: 'I want to live' }] },
|
||||
{ c: [{ d: 'and also I', g: 'I want to run in a stream' }] }
|
||||
]
|
||||
}
|
||||
console.log(redact(obj))
|
||||
11
node_modules/fast-redact/example/serialize-false.js
generated
vendored
11
node_modules/fast-redact/example/serialize-false.js
generated
vendored
@@ -1,11 +0,0 @@
|
||||
'use strict'
|
||||
const fastRedact = require('..')
|
||||
const redact = fastRedact({
|
||||
paths: ['a'],
|
||||
serialize: false
|
||||
})
|
||||
const o = { a: 1, b: 2 }
|
||||
console.log(redact(o) === o)
|
||||
console.log(o)
|
||||
console.log(redact.restore(o) === o)
|
||||
console.log(o)
|
||||
4
node_modules/fast-redact/example/serialize-function.js
generated
vendored
4
node_modules/fast-redact/example/serialize-function.js
generated
vendored
@@ -1,4 +0,0 @@
|
||||
'use strict'
|
||||
const fastRedact = require('..')
|
||||
const redact = fastRedact({ paths: ['a'], serialize: (o) => JSON.stringify(o, 0, 2) })
|
||||
console.log(redact({ a: 1, b: 2 }))
|
||||
9
node_modules/fast-redact/example/top-wildcard-object.js
generated
vendored
9
node_modules/fast-redact/example/top-wildcard-object.js
generated
vendored
@@ -1,9 +0,0 @@
|
||||
'use strict'
|
||||
const fastRedact = require('..')
|
||||
const redact = fastRedact({ paths: ['*.c.d'] })
|
||||
const obj = {
|
||||
x: { c: { d: 'hide me', e: 'leave me be' } },
|
||||
y: { c: { d: 'and me', f: 'I want to live' } },
|
||||
z: { c: { d: 'and also I', g: 'I want to run in a stream' } }
|
||||
}
|
||||
console.log(redact(obj))
|
||||
56
node_modules/fast-redact/index.js
generated
vendored
56
node_modules/fast-redact/index.js
generated
vendored
@@ -1,56 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const validator = require('./lib/validator')
|
||||
const parse = require('./lib/parse')
|
||||
const redactor = require('./lib/redactor')
|
||||
const restorer = require('./lib/restorer')
|
||||
const { groupRedact, nestedRedact } = require('./lib/modifiers')
|
||||
const state = require('./lib/state')
|
||||
const rx = require('./lib/rx')
|
||||
const validate = validator()
|
||||
const noop = (o) => o
|
||||
noop.restore = noop
|
||||
|
||||
const DEFAULT_CENSOR = '[REDACTED]'
|
||||
fastRedact.rx = rx
|
||||
fastRedact.validator = validator
|
||||
|
||||
module.exports = fastRedact
|
||||
|
||||
function fastRedact (opts = {}) {
|
||||
const paths = Array.from(new Set(opts.paths || []))
|
||||
const serialize = 'serialize' in opts ? (
|
||||
opts.serialize === false ? opts.serialize
|
||||
: (typeof opts.serialize === 'function' ? opts.serialize : JSON.stringify)
|
||||
) : JSON.stringify
|
||||
const remove = opts.remove
|
||||
if (remove === true && serialize !== JSON.stringify) {
|
||||
throw Error('fast-redact – remove option may only be set when serializer is JSON.stringify')
|
||||
}
|
||||
const censor = remove === true
|
||||
? undefined
|
||||
: 'censor' in opts ? opts.censor : DEFAULT_CENSOR
|
||||
|
||||
const isCensorFct = typeof censor === 'function'
|
||||
const censorFctTakesPath = isCensorFct && censor.length > 1
|
||||
|
||||
if (paths.length === 0) return serialize || noop
|
||||
|
||||
validate({ paths, serialize, censor })
|
||||
|
||||
const { wildcards, wcLen, secret } = parse({ paths, censor })
|
||||
|
||||
const compileRestore = restorer()
|
||||
const strict = 'strict' in opts ? opts.strict : true
|
||||
|
||||
return redactor({ secret, wcLen, serialize, strict, isCensorFct, censorFctTakesPath }, state({
|
||||
secret,
|
||||
censor,
|
||||
compileRestore,
|
||||
serialize,
|
||||
groupRedact,
|
||||
nestedRedact,
|
||||
wildcards,
|
||||
wcLen
|
||||
}))
|
||||
}
|
||||
291
node_modules/fast-redact/lib/modifiers.js
generated
vendored
291
node_modules/fast-redact/lib/modifiers.js
generated
vendored
@@ -1,291 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
groupRedact,
|
||||
groupRestore,
|
||||
nestedRedact,
|
||||
nestedRestore
|
||||
}
|
||||
|
||||
function groupRestore ({ keys, values, target }) {
|
||||
if (target == null || typeof target === 'string') return
|
||||
const length = keys.length
|
||||
for (var i = 0; i < length; i++) {
|
||||
const k = keys[i]
|
||||
target[k] = values[i]
|
||||
}
|
||||
}
|
||||
|
||||
function groupRedact (o, path, censor, isCensorFct, censorFctTakesPath) {
|
||||
const target = get(o, path)
|
||||
if (target == null || typeof target === 'string') return { keys: null, values: null, target, flat: true }
|
||||
const keys = Object.keys(target)
|
||||
const keysLength = keys.length
|
||||
const pathLength = path.length
|
||||
const pathWithKey = censorFctTakesPath ? [...path] : undefined
|
||||
const values = new Array(keysLength)
|
||||
|
||||
for (var i = 0; i < keysLength; i++) {
|
||||
const key = keys[i]
|
||||
values[i] = target[key]
|
||||
|
||||
if (censorFctTakesPath) {
|
||||
pathWithKey[pathLength] = key
|
||||
target[key] = censor(target[key], pathWithKey)
|
||||
} else if (isCensorFct) {
|
||||
target[key] = censor(target[key])
|
||||
} else {
|
||||
target[key] = censor
|
||||
}
|
||||
}
|
||||
return { keys, values, target, flat: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {RestoreInstruction[]} instructions a set of instructions for restoring values to objects
|
||||
*/
|
||||
function nestedRestore (instructions) {
|
||||
for (let i = 0; i < instructions.length; i++) {
|
||||
const { target, path, value } = instructions[i]
|
||||
let current = target
|
||||
for (let i = path.length - 1; i > 0; i--) {
|
||||
current = current[path[i]]
|
||||
}
|
||||
current[path[0]] = value
|
||||
}
|
||||
}
|
||||
|
||||
function nestedRedact (store, o, path, ns, censor, isCensorFct, censorFctTakesPath) {
|
||||
const target = get(o, path)
|
||||
if (target == null) return
|
||||
const keys = Object.keys(target)
|
||||
const keysLength = keys.length
|
||||
for (var i = 0; i < keysLength; i++) {
|
||||
const key = keys[i]
|
||||
specialSet(store, target, key, path, ns, censor, isCensorFct, censorFctTakesPath)
|
||||
}
|
||||
return store
|
||||
}
|
||||
|
||||
function has (obj, prop) {
|
||||
return obj !== undefined && obj !== null
|
||||
? ('hasOwn' in Object ? Object.hasOwn(obj, prop) : Object.prototype.hasOwnProperty.call(obj, prop))
|
||||
: false
|
||||
}
|
||||
|
||||
function specialSet (store, o, k, path, afterPath, censor, isCensorFct, censorFctTakesPath) {
|
||||
const afterPathLen = afterPath.length
|
||||
const lastPathIndex = afterPathLen - 1
|
||||
const originalKey = k
|
||||
var i = -1
|
||||
var n
|
||||
var nv
|
||||
var ov
|
||||
var oov = null
|
||||
var wc = null
|
||||
var kIsWc
|
||||
var wcov
|
||||
var consecutive = false
|
||||
var level = 0
|
||||
// need to track depth of the `redactPath` tree
|
||||
var depth = 0
|
||||
var redactPathCurrent = tree()
|
||||
ov = n = o[k]
|
||||
if (typeof n !== 'object') return
|
||||
while (n != null && ++i < afterPathLen) {
|
||||
depth += 1
|
||||
k = afterPath[i]
|
||||
oov = ov
|
||||
if (k !== '*' && !wc && !(typeof n === 'object' && k in n)) {
|
||||
break
|
||||
}
|
||||
if (k === '*') {
|
||||
if (wc === '*') {
|
||||
consecutive = true
|
||||
}
|
||||
wc = k
|
||||
if (i !== lastPathIndex) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if (wc) {
|
||||
const wcKeys = Object.keys(n)
|
||||
for (var j = 0; j < wcKeys.length; j++) {
|
||||
const wck = wcKeys[j]
|
||||
wcov = n[wck]
|
||||
kIsWc = k === '*'
|
||||
if (consecutive) {
|
||||
redactPathCurrent = node(redactPathCurrent, wck, depth)
|
||||
level = i
|
||||
ov = iterateNthLevel(wcov, level - 1, k, path, afterPath, censor, isCensorFct, censorFctTakesPath, originalKey, n, nv, ov, kIsWc, wck, i, lastPathIndex, redactPathCurrent, store, o[originalKey], depth + 1)
|
||||
} else {
|
||||
if (kIsWc || (typeof wcov === 'object' && wcov !== null && k in wcov)) {
|
||||
if (kIsWc) {
|
||||
ov = wcov
|
||||
} else {
|
||||
ov = wcov[k]
|
||||
}
|
||||
nv = (i !== lastPathIndex)
|
||||
? ov
|
||||
: (isCensorFct
|
||||
? (censorFctTakesPath ? censor(ov, [...path, originalKey, ...afterPath]) : censor(ov))
|
||||
: censor)
|
||||
if (kIsWc) {
|
||||
const rv = restoreInstr(node(redactPathCurrent, wck, depth), ov, o[originalKey])
|
||||
store.push(rv)
|
||||
n[wck] = nv
|
||||
} else {
|
||||
if (wcov[k] === nv) {
|
||||
// pass
|
||||
} else if ((nv === undefined && censor !== undefined) || (has(wcov, k) && nv === ov)) {
|
||||
redactPathCurrent = node(redactPathCurrent, wck, depth)
|
||||
} else {
|
||||
redactPathCurrent = node(redactPathCurrent, wck, depth)
|
||||
const rv = restoreInstr(node(redactPathCurrent, k, depth + 1), ov, o[originalKey])
|
||||
store.push(rv)
|
||||
wcov[k] = nv
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wc = null
|
||||
} else {
|
||||
ov = n[k]
|
||||
redactPathCurrent = node(redactPathCurrent, k, depth)
|
||||
nv = (i !== lastPathIndex)
|
||||
? ov
|
||||
: (isCensorFct
|
||||
? (censorFctTakesPath ? censor(ov, [...path, originalKey, ...afterPath]) : censor(ov))
|
||||
: censor)
|
||||
if ((has(n, k) && nv === ov) || (nv === undefined && censor !== undefined)) {
|
||||
// pass
|
||||
} else {
|
||||
const rv = restoreInstr(redactPathCurrent, ov, o[originalKey])
|
||||
store.push(rv)
|
||||
n[k] = nv
|
||||
}
|
||||
n = n[k]
|
||||
}
|
||||
if (typeof n !== 'object') break
|
||||
// prevent circular structure, see https://github.com/pinojs/pino/issues/1513
|
||||
if (ov === oov || typeof ov === 'undefined') {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function get (o, p) {
|
||||
var i = -1
|
||||
var l = p.length
|
||||
var n = o
|
||||
while (n != null && ++i < l) {
|
||||
n = n[p[i]]
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
function iterateNthLevel (wcov, level, k, path, afterPath, censor, isCensorFct, censorFctTakesPath, originalKey, n, nv, ov, kIsWc, wck, i, lastPathIndex, redactPathCurrent, store, parent, depth) {
|
||||
if (level === 0) {
|
||||
if (kIsWc || (typeof wcov === 'object' && wcov !== null && k in wcov)) {
|
||||
if (kIsWc) {
|
||||
ov = wcov
|
||||
} else {
|
||||
ov = wcov[k]
|
||||
}
|
||||
nv = (i !== lastPathIndex)
|
||||
? ov
|
||||
: (isCensorFct
|
||||
? (censorFctTakesPath ? censor(ov, [...path, originalKey, ...afterPath]) : censor(ov))
|
||||
: censor)
|
||||
if (kIsWc) {
|
||||
const rv = restoreInstr(redactPathCurrent, ov, parent)
|
||||
store.push(rv)
|
||||
n[wck] = nv
|
||||
} else {
|
||||
if (wcov[k] === nv) {
|
||||
// pass
|
||||
} else if ((nv === undefined && censor !== undefined) || (has(wcov, k) && nv === ov)) {
|
||||
// pass
|
||||
} else {
|
||||
const rv = restoreInstr(node(redactPathCurrent, k, depth + 1), ov, parent)
|
||||
store.push(rv)
|
||||
wcov[k] = nv
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const key in wcov) {
|
||||
if (typeof wcov[key] === 'object') {
|
||||
redactPathCurrent = node(redactPathCurrent, key, depth)
|
||||
iterateNthLevel(wcov[key], level - 1, k, path, afterPath, censor, isCensorFct, censorFctTakesPath, originalKey, n, nv, ov, kIsWc, wck, i, lastPathIndex, redactPathCurrent, store, parent, depth + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} TreeNode
|
||||
* @prop {TreeNode} [parent] reference to the parent of this node in the tree, or `null` if there is no parent
|
||||
* @prop {string} key the key that this node represents (key here being part of the path being redacted
|
||||
* @prop {TreeNode[]} children the child nodes of this node
|
||||
* @prop {number} depth the depth of this node in the tree
|
||||
*/
|
||||
|
||||
/**
|
||||
* instantiate a new, empty tree
|
||||
* @returns {TreeNode}
|
||||
*/
|
||||
function tree () {
|
||||
return { parent: null, key: null, children: [], depth: 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a new node in the tree, attaching it as a child of the provided parent node
|
||||
* if the specified depth matches the parent depth, adds the new node as a _sibling_ of the parent instead
|
||||
* @param {TreeNode} parent the parent node to add a new node to (if the parent depth matches the provided `depth` value, will instead add as a sibling of this
|
||||
* @param {string} key the key that the new node represents (key here being part of the path being redacted)
|
||||
* @param {number} depth the depth of the new node in the tree - used to determing whether to add the new node as a child or sibling of the provided `parent` node
|
||||
* @returns {TreeNode} a reference to the newly created node in the tree
|
||||
*/
|
||||
function node (parent, key, depth) {
|
||||
if (parent.depth === depth) {
|
||||
return node(parent.parent, key, depth)
|
||||
}
|
||||
|
||||
var child = {
|
||||
parent,
|
||||
key,
|
||||
depth,
|
||||
children: []
|
||||
}
|
||||
|
||||
parent.children.push(child)
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {object} RestoreInstruction
|
||||
* @prop {string[]} path a reverse-order path that can be used to find the correct insertion point to restore a `value` for the given `parent` object
|
||||
* @prop {*} value the value to restore
|
||||
* @prop {object} target the object to restore the `value` in
|
||||
*/
|
||||
|
||||
/**
|
||||
* create a restore instruction for the given redactPath node
|
||||
* generates a path in reverse order by walking up the redactPath tree
|
||||
* @param {TreeNode} node a tree node that should be at the bottom of the redact path (i.e. have no children) - this will be used to walk up the redact path tree to construct the path needed to restore
|
||||
* @param {*} value the value to restore
|
||||
* @param {object} target a reference to the parent object to apply the restore instruction to
|
||||
* @returns {RestoreInstruction} an instruction used to restore a nested value for a specific object
|
||||
*/
|
||||
function restoreInstr (node, value, target) {
|
||||
let current = node
|
||||
const path = []
|
||||
do {
|
||||
path.push(current.key)
|
||||
current = current.parent
|
||||
} while (current.parent != null)
|
||||
|
||||
return { path, value, target }
|
||||
}
|
||||
44
node_modules/fast-redact/lib/parse.js
generated
vendored
44
node_modules/fast-redact/lib/parse.js
generated
vendored
@@ -1,44 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const rx = require('./rx')
|
||||
|
||||
module.exports = parse
|
||||
|
||||
function parse ({ paths }) {
|
||||
const wildcards = []
|
||||
var wcLen = 0
|
||||
const secret = paths.reduce(function (o, strPath, ix) {
|
||||
var path = strPath.match(rx).map((p) => p.replace(/'|"|`/g, ''))
|
||||
const leadingBracket = strPath[0] === '['
|
||||
path = path.map((p) => {
|
||||
if (p[0] === '[') return p.substr(1, p.length - 2)
|
||||
else return p
|
||||
})
|
||||
const star = path.indexOf('*')
|
||||
if (star > -1) {
|
||||
const before = path.slice(0, star)
|
||||
const beforeStr = before.join('.')
|
||||
const after = path.slice(star + 1, path.length)
|
||||
const nested = after.length > 0
|
||||
wcLen++
|
||||
wildcards.push({
|
||||
before,
|
||||
beforeStr,
|
||||
after,
|
||||
nested
|
||||
})
|
||||
} else {
|
||||
o[strPath] = {
|
||||
path: path,
|
||||
val: undefined,
|
||||
precensored: false,
|
||||
circle: '',
|
||||
escPath: JSON.stringify(strPath),
|
||||
leadingBracket: leadingBracket
|
||||
}
|
||||
}
|
||||
return o
|
||||
}, {})
|
||||
|
||||
return { wildcards, wcLen, secret }
|
||||
}
|
||||
108
node_modules/fast-redact/lib/redactor.js
generated
vendored
108
node_modules/fast-redact/lib/redactor.js
generated
vendored
@@ -1,108 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const rx = require('./rx')
|
||||
|
||||
module.exports = redactor
|
||||
|
||||
function redactor ({ secret, serialize, wcLen, strict, isCensorFct, censorFctTakesPath }, state) {
|
||||
/* eslint-disable-next-line */
|
||||
const redact = Function('o', `
|
||||
if (typeof o !== 'object' || o == null) {
|
||||
${strictImpl(strict, serialize)}
|
||||
}
|
||||
const { censor, secret } = this
|
||||
const originalSecret = {}
|
||||
const secretKeys = Object.keys(secret)
|
||||
for (var i = 0; i < secretKeys.length; i++) {
|
||||
originalSecret[secretKeys[i]] = secret[secretKeys[i]]
|
||||
}
|
||||
|
||||
${redactTmpl(secret, isCensorFct, censorFctTakesPath)}
|
||||
this.compileRestore()
|
||||
${dynamicRedactTmpl(wcLen > 0, isCensorFct, censorFctTakesPath)}
|
||||
this.secret = originalSecret
|
||||
${resultTmpl(serialize)}
|
||||
`).bind(state)
|
||||
|
||||
redact.state = state
|
||||
|
||||
if (serialize === false) {
|
||||
redact.restore = (o) => state.restore(o)
|
||||
}
|
||||
|
||||
return redact
|
||||
}
|
||||
|
||||
function redactTmpl (secret, isCensorFct, censorFctTakesPath) {
|
||||
return Object.keys(secret).map((path) => {
|
||||
const { escPath, leadingBracket, path: arrPath } = secret[path]
|
||||
const skip = leadingBracket ? 1 : 0
|
||||
const delim = leadingBracket ? '' : '.'
|
||||
const hops = []
|
||||
var match
|
||||
while ((match = rx.exec(path)) !== null) {
|
||||
const [ , ix ] = match
|
||||
const { index, input } = match
|
||||
if (index > skip) hops.push(input.substring(0, index - (ix ? 0 : 1)))
|
||||
}
|
||||
var existence = hops.map((p) => `o${delim}${p}`).join(' && ')
|
||||
if (existence.length === 0) existence += `o${delim}${path} != null`
|
||||
else existence += ` && o${delim}${path} != null`
|
||||
|
||||
const circularDetection = `
|
||||
switch (true) {
|
||||
${hops.reverse().map((p) => `
|
||||
case o${delim}${p} === censor:
|
||||
secret[${escPath}].circle = ${JSON.stringify(p)}
|
||||
break
|
||||
`).join('\n')}
|
||||
}
|
||||
`
|
||||
|
||||
const censorArgs = censorFctTakesPath
|
||||
? `val, ${JSON.stringify(arrPath)}`
|
||||
: `val`
|
||||
|
||||
return `
|
||||
if (${existence}) {
|
||||
const val = o${delim}${path}
|
||||
if (val === censor) {
|
||||
secret[${escPath}].precensored = true
|
||||
} else {
|
||||
secret[${escPath}].val = val
|
||||
o${delim}${path} = ${isCensorFct ? `censor(${censorArgs})` : 'censor'}
|
||||
${circularDetection}
|
||||
}
|
||||
}
|
||||
`
|
||||
}).join('\n')
|
||||
}
|
||||
|
||||
function dynamicRedactTmpl (hasWildcards, isCensorFct, censorFctTakesPath) {
|
||||
return hasWildcards === true ? `
|
||||
{
|
||||
const { wildcards, wcLen, groupRedact, nestedRedact } = this
|
||||
for (var i = 0; i < wcLen; i++) {
|
||||
const { before, beforeStr, after, nested } = wildcards[i]
|
||||
if (nested === true) {
|
||||
secret[beforeStr] = secret[beforeStr] || []
|
||||
nestedRedact(secret[beforeStr], o, before, after, censor, ${isCensorFct}, ${censorFctTakesPath})
|
||||
} else secret[beforeStr] = groupRedact(o, before, censor, ${isCensorFct}, ${censorFctTakesPath})
|
||||
}
|
||||
}
|
||||
` : ''
|
||||
}
|
||||
|
||||
function resultTmpl (serialize) {
|
||||
return serialize === false ? `return o` : `
|
||||
var s = this.serialize(o)
|
||||
this.restore(o)
|
||||
return s
|
||||
`
|
||||
}
|
||||
|
||||
function strictImpl (strict, serialize) {
|
||||
return strict === true
|
||||
? `throw Error('fast-redact: primitives cannot be redacted')`
|
||||
: serialize === false ? `return o` : `return this.serialize(o)`
|
||||
}
|
||||
92
node_modules/fast-redact/lib/restorer.js
generated
vendored
92
node_modules/fast-redact/lib/restorer.js
generated
vendored
@@ -1,92 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const { groupRestore, nestedRestore } = require('./modifiers')
|
||||
|
||||
module.exports = restorer
|
||||
|
||||
function restorer () {
|
||||
return function compileRestore () {
|
||||
if (this.restore) {
|
||||
this.restore.state.secret = this.secret
|
||||
return
|
||||
}
|
||||
const { secret, wcLen } = this
|
||||
const paths = Object.keys(secret)
|
||||
const resetters = resetTmpl(secret, paths)
|
||||
const hasWildcards = wcLen > 0
|
||||
const state = hasWildcards ? { secret, groupRestore, nestedRestore } : { secret }
|
||||
/* eslint-disable-next-line */
|
||||
this.restore = Function(
|
||||
'o',
|
||||
restoreTmpl(resetters, paths, hasWildcards)
|
||||
).bind(state)
|
||||
this.restore.state = state
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mutates the original object to be censored by restoring its original values
|
||||
* prior to censoring.
|
||||
*
|
||||
* @param {object} secret Compiled object describing which target fields should
|
||||
* be censored and the field states.
|
||||
* @param {string[]} paths The list of paths to censor as provided at
|
||||
* initialization time.
|
||||
*
|
||||
* @returns {string} String of JavaScript to be used by `Function()`. The
|
||||
* string compiles to the function that does the work in the description.
|
||||
*/
|
||||
function resetTmpl (secret, paths) {
|
||||
return paths.map((path) => {
|
||||
const { circle, escPath, leadingBracket } = secret[path]
|
||||
const delim = leadingBracket ? '' : '.'
|
||||
const reset = circle
|
||||
? `o.${circle} = secret[${escPath}].val`
|
||||
: `o${delim}${path} = secret[${escPath}].val`
|
||||
const clear = `secret[${escPath}].val = undefined`
|
||||
return `
|
||||
if (secret[${escPath}].val !== undefined) {
|
||||
try { ${reset} } catch (e) {}
|
||||
${clear}
|
||||
}
|
||||
`
|
||||
}).join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the body of the restore function
|
||||
*
|
||||
* Restoration of the redacted object happens
|
||||
* backwards, in reverse order of redactions,
|
||||
* so that repeated redactions on the same object
|
||||
* property can be eventually rolled back to the
|
||||
* original value.
|
||||
*
|
||||
* This way dynamic redactions are restored first,
|
||||
* starting from the last one working backwards and
|
||||
* followed by the static ones.
|
||||
*
|
||||
* @returns {string} the body of the restore function
|
||||
*/
|
||||
function restoreTmpl (resetters, paths, hasWildcards) {
|
||||
const dynamicReset = hasWildcards === true ? `
|
||||
const keys = Object.keys(secret)
|
||||
const len = keys.length
|
||||
for (var i = len - 1; i >= ${paths.length}; i--) {
|
||||
const k = keys[i]
|
||||
const o = secret[k]
|
||||
if (o) {
|
||||
if (o.flat === true) this.groupRestore(o)
|
||||
else this.nestedRestore(o)
|
||||
secret[k] = null
|
||||
}
|
||||
}
|
||||
` : ''
|
||||
|
||||
return `
|
||||
const secret = this.secret
|
||||
${dynamicReset}
|
||||
${resetters}
|
||||
return o
|
||||
`
|
||||
}
|
||||
16
node_modules/fast-redact/lib/rx.js
generated
vendored
16
node_modules/fast-redact/lib/rx.js
generated
vendored
@@ -1,16 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = /[^.[\]]+|\[((?:.)*?)\]/g
|
||||
|
||||
/*
|
||||
Regular expression explanation:
|
||||
|
||||
Alt 1: /[^.[\]]+/ - Match one or more characters that are *not* a dot (.)
|
||||
opening square bracket ([) or closing square bracket (])
|
||||
|
||||
Alt 2: /\[((?:.)*?)\]/ - If the char IS dot or square bracket, then create a capture
|
||||
group (which will be capture group $1) that matches anything
|
||||
within square brackets. Expansion is lazy so it will
|
||||
stop matching as soon as the first closing bracket is met `]`
|
||||
(rather than continuing to match until the final closing bracket).
|
||||
*/
|
||||
20
node_modules/fast-redact/lib/state.js
generated
vendored
20
node_modules/fast-redact/lib/state.js
generated
vendored
@@ -1,20 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = state
|
||||
|
||||
function state (o) {
|
||||
const {
|
||||
secret,
|
||||
censor,
|
||||
compileRestore,
|
||||
serialize,
|
||||
groupRedact,
|
||||
nestedRedact,
|
||||
wildcards,
|
||||
wcLen
|
||||
} = o
|
||||
const builder = [{ secret, censor, compileRestore }]
|
||||
if (serialize !== false) builder.push({ serialize })
|
||||
if (wcLen > 0) builder.push({ groupRedact, nestedRedact, wildcards, wcLen })
|
||||
return Object.assign(...builder)
|
||||
}
|
||||
33
node_modules/fast-redact/lib/validator.js
generated
vendored
33
node_modules/fast-redact/lib/validator.js
generated
vendored
@@ -1,33 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = validator
|
||||
|
||||
function validator (opts = {}) {
|
||||
const {
|
||||
ERR_PATHS_MUST_BE_STRINGS = () => 'fast-redact - Paths must be (non-empty) strings',
|
||||
ERR_INVALID_PATH = (s) => `fast-redact – Invalid path (${s})`
|
||||
} = opts
|
||||
|
||||
return function validate ({ paths }) {
|
||||
paths.forEach((s) => {
|
||||
if (typeof s !== 'string') {
|
||||
throw Error(ERR_PATHS_MUST_BE_STRINGS())
|
||||
}
|
||||
try {
|
||||
if (/〇/.test(s)) throw Error()
|
||||
const expr = (s[0] === '[' ? '' : '.') + s.replace(/^\*/, '〇').replace(/\.\*/g, '.〇').replace(/\[\*\]/g, '[〇]')
|
||||
if (/\n|\r|;/.test(expr)) throw Error()
|
||||
if (/\/\*/.test(expr)) throw Error()
|
||||
/* eslint-disable-next-line */
|
||||
Function(`
|
||||
'use strict'
|
||||
const o = new Proxy({}, { get: () => o, set: () => { throw Error() } });
|
||||
const 〇 = null;
|
||||
o${expr}
|
||||
if ([o${expr}].length !== 1) throw Error()`)()
|
||||
} catch (e) {
|
||||
throw Error(ERR_INVALID_PATH(s))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
50
node_modules/fast-redact/package.json
generated
vendored
50
node_modules/fast-redact/package.json
generated
vendored
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"name": "fast-redact",
|
||||
"version": "3.5.0",
|
||||
"description": "very fast object redaction",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "tap test",
|
||||
"posttest": "standard index.js 'lib/*.js' 'example/*.js' benchmark/index.js test/index.js | snazzy",
|
||||
"cov": "tap --cov test",
|
||||
"cov-ui": "tap --coverage-report=html test",
|
||||
"ci": "tap --cov --100 test",
|
||||
"bench": "node benchmark"
|
||||
},
|
||||
"keywords": [
|
||||
"redact",
|
||||
"censor",
|
||||
"performance",
|
||||
"performant",
|
||||
"gdpr",
|
||||
"fast",
|
||||
"speed",
|
||||
"serialize",
|
||||
"stringify"
|
||||
],
|
||||
"author": "David Mark Clements <david.clements@nearform.com>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"fastbench": "^1.0.1",
|
||||
"pino-noir": "^2.2.1",
|
||||
"snazzy": "^8.0.0",
|
||||
"standard": "^12.0.1",
|
||||
"tap": "^12.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"directories": {
|
||||
"example": "example",
|
||||
"lib": "lib",
|
||||
"test": "test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/davidmarkclements/fast-redact.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/davidmarkclements/fast-redact/issues"
|
||||
},
|
||||
"homepage": "https://github.com/davidmarkclements/fast-redact#readme"
|
||||
}
|
||||
282
node_modules/fast-redact/readme.md
generated
vendored
282
node_modules/fast-redact/readme.md
generated
vendored
@@ -1,282 +0,0 @@
|
||||
# fast-redact
|
||||
|
||||
very fast object redaction
|
||||
|
||||
[](https://travis-ci.org/davidmarkclements/fast-redact)
|
||||
|
||||
## Default Usage
|
||||
|
||||
By default, `fast-redact` serializes an object with `JSON.stringify`, censoring any
|
||||
data at paths specified:
|
||||
|
||||
```js
|
||||
const fastRedact = require('fast-redact')
|
||||
const fauxRequest = {
|
||||
headers: {
|
||||
host: 'http://example.com',
|
||||
cookie: `oh oh we don't want this exposed in logs in etc.`,
|
||||
referer: `if we're cool maybe we'll even redact this`,
|
||||
// Note: headers often contain hyphens and require bracket notation
|
||||
'X-Forwarded-For': `192.168.0.1`
|
||||
}
|
||||
}
|
||||
const redact = fastRedact({
|
||||
paths: ['headers.cookie', 'headers.referer', 'headers["X-Forwarded-For"]']
|
||||
})
|
||||
|
||||
console.log(redact(fauxRequest))
|
||||
// {"headers":{"host":"http://example.com","cookie":"[REDACTED]","referer":"[REDACTED]","X-Forwarded-For": "[REDACTED]"}}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### `require('fast-redact')({paths, censor, serialize}) => Function`
|
||||
|
||||
When called without any options, or with a zero length `paths` array,
|
||||
`fast-redact` will return `JSON.stringify` or the `serialize` option, if set.
|
||||
|
||||
#### `paths` – `Array`
|
||||
|
||||
An array of strings describing the nested location of a key in an object.
|
||||
|
||||
The syntax follows that of the EcmaScript specification, that is any JavaScript
|
||||
path is accepted – both bracket and dot notation is supported. For instance in
|
||||
each of the following cases, the `c` property will be redacted: `a.b.c`,`a['b'].c`,
|
||||
`a["b"].c`, `a[``b``].c`. Since bracket notation is supported, array indices are also
|
||||
supported `a[0].b` would redact the `b` key in the first object of the `a` array.
|
||||
|
||||
Leading brackets are also allowed, for instance `["a"].b.c` will work.
|
||||
|
||||
##### Wildcards
|
||||
|
||||
In addition to static paths, asterisk wildcards are also supported.
|
||||
|
||||
When an asterisk is place in the final position it will redact all keys within the
|
||||
parent object. For instance `a.b.*` will redact all keys in the `b` object. Similarly
|
||||
for arrays `a.b[*]` will redact all elements of an array (in truth it actually doesn't matter
|
||||
whether `b` is in an object or array in either case, both notation styles will work).
|
||||
|
||||
When an asterisk is in an intermediate or first position, the paths following the asterisk will
|
||||
be redacted for every object within the parent.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
const fastRedact = require('fast-redact')
|
||||
const redact = fastRedact({paths: ['*.c.d']})
|
||||
const obj = {
|
||||
x: {c: {d: 'hide me', e: 'leave me be'}},
|
||||
y: {c: {d: 'and me', f: 'I want to live'}},
|
||||
z: {c: {d: 'and also I', g: 'I want to run in a stream'}}
|
||||
}
|
||||
console.log(redact(obj))
|
||||
// {"x":{"c":{"d":"[REDACTED]","e":"leave me be"}},"y":{"c":{"d":"[REDACTED]","f":"I want to live"}},"z":{"c":{"d":"[REDACTED]","g":"I want to run in a stream"}}}
|
||||
```
|
||||
|
||||
Another example with a nested array:
|
||||
|
||||
```js
|
||||
const fastRedact = require('..')
|
||||
const redact = fastRedact({paths: ['a[*].c.d']})
|
||||
const obj = {
|
||||
a: [
|
||||
{c: {d: 'hide me', e: 'leave me be'}},
|
||||
{c: {d: 'and me', f: 'I want to live'}},
|
||||
{c: {d: 'and also I', g: 'I want to run in a stream'}}
|
||||
]
|
||||
}
|
||||
console.log(redact(obj))
|
||||
// {"a":[{"c":{"d":"[REDACTED]","e":"leave me be"}},{"c":{"d":"[REDACTED]","f":"I want to live"}},{"c":{"d":"[REDACTED]","g":"I want to run in a stream"}}]}
|
||||
```
|
||||
|
||||
#### `remove` - `Boolean` - `[false]`
|
||||
|
||||
The `remove` option, when set to `true` will cause keys to be removed from the
|
||||
serialized output.
|
||||
|
||||
Since the implementation exploits the fact that `undefined` keys are ignored
|
||||
by `JSON.stringify` the `remove` option may *only* be used when `JSON.stringify`
|
||||
is the serializer (this is the default) – otherwise `fast-redact` will throw.
|
||||
|
||||
If supplying a custom serializer that has the same behavior (removing keys
|
||||
with `undefined` values), this restriction can be bypassed by explicitly setting
|
||||
the `censor` to `undefined`.
|
||||
|
||||
|
||||
#### `censor` – `<Any type>` – `('[REDACTED]')`
|
||||
|
||||
This is the value which overwrites redacted properties.
|
||||
|
||||
Setting `censor` to `undefined` will cause properties to removed as long as this is
|
||||
the behavior of the `serializer` – which defaults to `JSON.stringify`, which does
|
||||
remove `undefined` properties.
|
||||
|
||||
Setting `censor` to a function will cause `fast-redact` to invoke it with the original
|
||||
value. The output of the `censor` function sets the redacted value.
|
||||
Please note that asynchronous functions are not supported.
|
||||
|
||||
#### `serialize` – `Function | Boolean` – `(JSON.stringify)`
|
||||
|
||||
The `serialize` option may either be a function or a boolean. If a function is supplied, this
|
||||
will be used to `serialize` the redacted object. It's important to understand that for
|
||||
performance reasons `fast-redact` *mutates* the original object, then serializes, then
|
||||
restores the original values. So the object passed to the serializer is the exact same
|
||||
object passed to the redacting function.
|
||||
|
||||
The `serialize` option as a function example:
|
||||
|
||||
```js
|
||||
const fastRedact = require('fast-redact')
|
||||
const redact = fastRedact({
|
||||
paths: ['a'],
|
||||
serialize: (o) => JSON.stringify(o, 0, 2)
|
||||
})
|
||||
console.log(redact({a: 1, b: 2}))
|
||||
// {
|
||||
// "a": "[REDACTED]",
|
||||
// "b": 2
|
||||
// }
|
||||
```
|
||||
|
||||
For advanced usage the `serialize` option can be set to `false`. When `serialize` is set to `false`,
|
||||
instead of the serialized object, the output of the redactor function will be the mutated object
|
||||
itself (this is the exact same as the object passed in). In addition a `restore` method is supplied
|
||||
on the redactor function allowing the redacted keys to be restored with the original data.
|
||||
|
||||
```js
|
||||
const fastRedact = require('fast-redact')
|
||||
const redact = fastRedact({
|
||||
paths: ['a'],
|
||||
serialize: false
|
||||
})
|
||||
const o = {a: 1, b: 2}
|
||||
console.log(redact(o) === o) // true
|
||||
console.log(o) // { a: '[REDACTED]', b: 2 }
|
||||
console.log(redact.restore(o) === o) // true
|
||||
console.log(o) // { a: 1, b: 2 }
|
||||
```
|
||||
|
||||
#### `strict` – `Boolean` - `[true]`
|
||||
The `strict` option, when set to `true`, will cause the redactor function to throw if instead
|
||||
of an object it finds a primitive. When `strict` is set to `false`, the redactor function
|
||||
will treat the primitive value as having already been redacted, and return it serialized (with
|
||||
`JSON.stringify` or the user's custom `serialize` function), or as-is if the `serialize` option
|
||||
was set to false.
|
||||
|
||||
## Approach
|
||||
|
||||
In order to achieve lowest cost/highest performance redaction `fast-redact`
|
||||
creates and compiles a function (using the `Function` constructor) on initialization.
|
||||
It's important to distinguish this from the dangers of a runtime eval, no user input
|
||||
is involved in creating the string that compiles into the function. This is as safe
|
||||
as writing code normally and having it compiled by V8 in the usual way.
|
||||
|
||||
Thanks to changes in V8 in recent years, state can be injected into compiled functions
|
||||
using `bind` at very low cost (whereas `bind` used to be expensive, and getting state
|
||||
into a compiled function by any means was difficult without a performance penalty).
|
||||
|
||||
For static paths, this function simply checks that the path exists and then overwrites
|
||||
with the censor. Wildcard paths are processed with normal functions that iterate over
|
||||
the object redacting values as necessary.
|
||||
|
||||
It's important to note, that the original object is mutated – for performance reasons
|
||||
a copy is not made. See [rfdc](https://github.com/davidmarkclements/rfdc) (Really Fast
|
||||
Deep Clone) for the fastest known way to clone – it's not nearly close enough in speed
|
||||
to editing the original object, serializing and then restoring values.
|
||||
|
||||
A `restore` function is also created and compiled to put the original state back on
|
||||
to the object after redaction. This means that in the default usage case, the operation
|
||||
is essentially atomic - the object is mutated, serialized and restored internally which
|
||||
avoids any state management issues.
|
||||
|
||||
## Caveat
|
||||
|
||||
As mentioned in approach, the `paths` array input is dynamically compiled into a function
|
||||
at initialization time. While the `paths` array is vigourously tested for any developer
|
||||
errors, it's strongly recommended against allowing user input to directly supply any
|
||||
paths to redact. It can't be guaranteed that allowing user input for `paths` couldn't
|
||||
feasibly expose an attack vector.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
The fastest known predecessor to `fast-redact` is the non-generic [`pino-noir`](http://npm.im/pino-noir)
|
||||
library (which was also written by myself).
|
||||
|
||||
In the direct calling case, `fast-redact` is ~30x faster than `pino-noir`, however a more realistic
|
||||
comparison is overhead on `JSON.stringify`.
|
||||
|
||||
For a static redaction case (no wildcards) `pino-noir` adds ~25% overhead on top of `JSON.stringify`
|
||||
whereas `fast-redact` adds ~1% overhead.
|
||||
|
||||
In the basic last-position wildcard case,`fast-redact` is ~12% faster than `pino-noir`.
|
||||
|
||||
The `pino-noir` module does not support intermediate wildcards, but `fast-redact` does,
|
||||
the cost of an intermediate wildcard that results in two keys over two nested objects
|
||||
being redacted is about 25% overhead on `JSON.stringify`. The cost of an intermediate
|
||||
wildcard that results in four keys across two objects being redacted is about 55% overhead
|
||||
on `JSON.stringify` and ~50% more expensive that explicitly declaring the keys.
|
||||
|
||||
```sh
|
||||
npm run bench
|
||||
```
|
||||
|
||||
```
|
||||
benchNoirV2*500: 59.108ms
|
||||
benchFastRedact*500: 2.483ms
|
||||
benchFastRedactRestore*500: 10.904ms
|
||||
benchNoirV2Wild*500: 91.399ms
|
||||
benchFastRedactWild*500: 21.200ms
|
||||
benchFastRedactWildRestore*500: 27.304ms
|
||||
benchFastRedactIntermediateWild*500: 92.304ms
|
||||
benchFastRedactIntermediateWildRestore*500: 107.047ms
|
||||
benchJSONStringify*500: 210.573ms
|
||||
benchNoirV2Serialize*500: 281.148ms
|
||||
benchFastRedactSerialize*500: 215.845ms
|
||||
benchNoirV2WildSerialize*500: 281.168ms
|
||||
benchFastRedactWildSerialize*500: 247.140ms
|
||||
benchFastRedactIntermediateWildSerialize*500: 333.722ms
|
||||
benchFastRedactIntermediateWildMatchWildOutcomeSerialize*500: 463.667ms
|
||||
benchFastRedactStaticMatchWildOutcomeSerialize*500: 239.293ms
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
```
|
||||
224 passing (499.544ms)
|
||||
```
|
||||
|
||||
### Coverage
|
||||
|
||||
```
|
||||
npm run cov
|
||||
```
|
||||
|
||||
```
|
||||
-----------------|----------|----------|----------|----------|-------------------|
|
||||
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
|
||||
-----------------|----------|----------|----------|----------|-------------------|
|
||||
All files | 100 | 100 | 100 | 100 | |
|
||||
fast-redact | 100 | 100 | 100 | 100 | |
|
||||
index.js | 100 | 100 | 100 | 100 | |
|
||||
fast-redact/lib | 100 | 100 | 100 | 100 | |
|
||||
modifiers.js | 100 | 100 | 100 | 100 | |
|
||||
parse.js | 100 | 100 | 100 | 100 | |
|
||||
redactor.js | 100 | 100 | 100 | 100 | |
|
||||
restorer.js | 100 | 100 | 100 | 100 | |
|
||||
rx.js | 100 | 100 | 100 | 100 | |
|
||||
state.js | 100 | 100 | 100 | 100 | |
|
||||
validator.js | 100 | 100 | 100 | 100 | |
|
||||
-----------------|----------|----------|----------|----------|-------------------|
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Sponsored by [nearForm](http://www.nearform.com)
|
||||
1502
node_modules/fast-redact/test/index.js
generated
vendored
1502
node_modules/fast-redact/test/index.js
generated
vendored
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user