@hackylabs/deep-redact 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +36 -56
- package/dist/cjs/index.js +10 -131
- package/dist/cjs/utils/index.js +429 -0
- package/dist/cjs/utils/standardTransformers/bigint.js +10 -0
- package/dist/cjs/utils/standardTransformers/date.js +9 -0
- package/dist/cjs/utils/standardTransformers/error.js +16 -0
- package/dist/cjs/utils/standardTransformers/index.js +19 -0
- package/dist/cjs/utils/standardTransformers/map.js +9 -0
- package/dist/cjs/utils/standardTransformers/regex.js +15 -0
- package/dist/cjs/utils/standardTransformers/set.js +9 -0
- package/dist/cjs/utils/standardTransformers/url.js +9 -0
- package/dist/esm/index.mjs +9 -128
- package/dist/esm/utils/index.mjs +423 -0
- package/dist/esm/utils/standardTransformers/bigint.js +6 -0
- package/dist/esm/utils/standardTransformers/date.js +5 -0
- package/dist/esm/utils/standardTransformers/error.js +12 -0
- package/dist/esm/utils/standardTransformers/index.js +16 -0
- package/dist/esm/utils/standardTransformers/map.js +5 -0
- package/dist/esm/utils/standardTransformers/regex.js +11 -0
- package/dist/esm/utils/standardTransformers/set.js +5 -0
- package/dist/esm/utils/standardTransformers/url.js +5 -0
- package/dist/types/index.d.ts +3 -38
- package/dist/types/types.d.ts +48 -17
- package/dist/types/utils/index.d.ts +130 -0
- package/dist/types/utils/standardTransformers/bigint.d.ts +2 -0
- package/dist/types/utils/standardTransformers/date.d.ts +2 -0
- package/dist/types/utils/standardTransformers/error.d.ts +2 -0
- package/dist/types/utils/standardTransformers/index.d.ts +2 -0
- package/dist/types/utils/standardTransformers/map.d.ts +2 -0
- package/dist/types/utils/standardTransformers/regex.d.ts +2 -0
- package/dist/types/utils/standardTransformers/set.d.ts +2 -0
- package/dist/types/utils/standardTransformers/url.d.ts +2 -0
- package/package.json +66 -13
- package/dist/cjs/utils/redactorUtils.js +0 -263
- package/dist/esm/utils/redactorUtils.mjs +0 -264
- package/dist/types/utils/redactorUtils.d.ts +0 -91
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2025 Benjamin Green (https://bengreen.dev)
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
[](https://badge.fury.io/js/@hackylabs%2Fdeep-redact)
|
|
4
4
|
[](https://github.com/hackylabs/deep-redact/blob/main/LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
Safer and more configurable than many other redaction solutions, Deep Redact is a zero-dependency tool that redacts
|
|
7
|
+
sensitive information from strings and objects. It is designed to be used in a production environment where sensitive
|
|
8
|
+
information needs to be redacted from logs, error messages, files, and other outputs. Supporting both strings and objects
|
|
9
|
+
or a mix of both, Deep Redact can be used to redact sensitive information from more data structures than any other
|
|
10
|
+
redaction library. Even partially redacting sensitive information from strings is supported, by way of custom regex
|
|
11
|
+
patterns and replacers.
|
|
12
12
|
|
|
13
13
|
Circular references and other unsupported values are handled gracefully, and the library is designed to be as fast as
|
|
14
14
|
possible while still being easy to use and configure.
|
|
@@ -73,6 +73,30 @@ strRedaction.redact('<email>someone@somewhere.com</email><keepThis>This is fine<
|
|
|
73
73
|
// '<email>[REDACTED]</email><keepThis>This is fine</keepThis><password>[REDACTED]</password>'
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
// Override the `unsupportedTransformer` method to handle unsupported values
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
class CustomRedaction extends DeepRedact {
|
|
80
|
+
constructor(options) {
|
|
81
|
+
super(options)
|
|
82
|
+
this.rewriteUnsupported = (value) => {
|
|
83
|
+
if (value instanceof BigInt) return value.toString()
|
|
84
|
+
|
|
85
|
+
// Add more conditional statements for unsupported value types here (e.g. Error, Date, Map, Set, etc.)
|
|
86
|
+
|
|
87
|
+
// If the value is supported, return it
|
|
88
|
+
return value
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const customRedaction = new CustomRedaction({
|
|
94
|
+
blacklistedKeys: ['sensitive', 'password', /name/i],
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
customRedaction.redact({ a: BigInt(1) })
|
|
98
|
+
```
|
|
99
|
+
|
|
76
100
|
## Configuration
|
|
77
101
|
|
|
78
102
|
### Main Options
|
|
@@ -80,8 +104,7 @@ strRedaction.redact('<email>someone@somewhere.com</email><keepThis>This is fine<
|
|
|
80
104
|
| key | description | type | options | default | required |
|
|
81
105
|
| --- | --- | --- | --- | --- | --- |
|
|
82
106
|
| blacklistedKeys | Deeply compare names of these keys against the keys in your object. | array | Array<string│RegExp│BlacklistKeyConfig> | [] | N |
|
|
83
|
-
| stringTests | Array of regular expressions to perform against string values, whether that value is a flat string or nested within an object.
|
|
84
|
-
| partialStringTests | Array of regular expressions to perform against string values, whether that value is a flat string or nested within an object. Will redact only the matched part of the string using the replacer function provided in the config for the associated test. | array | StringTestConfig[] | [] | N |
|
|
107
|
+
| stringTests | Array of regular expressions to perform against string values, whether that value is a flat string or nested within an object. Can redact whole or partial string values. If a replacer function is provided in the config for the associated test, it will be used to redact the value if the value matches the test. | array | Array<RegExp│StringTestConfig> | [] | N |
|
|
85
108
|
| fuzzyKeyMatch | Loosely compare key names by checking if the key name of your unredacted object is included anywhere within the name of your blacklisted key. For example, is "pass" (your key) included in "password" (from config). | boolean | | false | N |
|
|
86
109
|
| caseSensitiveKeyMatch | Loosely compare key names by normalising the strings. This involves removing non-word characters and transforms the string to lowercase. This means you never have to worry having to list duplicate keys in different formats such as snake_case, camelCase, PascalCase or any other case. | boolean | | true | N |
|
|
87
110
|
| remove | Determines whether or not to remove the key from the object when it is redacted. | boolean | | false | N |
|
|
@@ -89,8 +112,9 @@ strRedaction.redact('<email>someone@somewhere.com</email><keepThis>This is fine<
|
|
|
89
112
|
| replacement | When a value is going to be redacted, what would you like to replace it with? | string │ function | | [REDACTED] | N |
|
|
90
113
|
| replaceStringByLength | When a string value is going to be replaced, optionally replace it by repeating the `replacement` to match the length of the value. For example, if `replaceStringByLength` were set to `true` and `replacement` was set to "x", then redacting "secret" would return "xxxxxx". This is sometimes useful for debugging purposes, although it may be less secure as it could give hints to the original value. | boolean | | false | N |
|
|
91
114
|
| types | JS types (values of `typeof` keyword). Only values with a typeof equal to `string`, `number`, `bigint`, `boolean`, `symbol`, `object`, or `function` will be redacted. Undefined values will never be redacted, although the type `undefined` is included in this list to keep TypeScript happy. | array | Array<'string'│'number'│'bigint'│'boolean'│'symbol'│'undefined'│'object'│'function'> | ['string'] | N |
|
|
92
|
-
| serialise | Determines whether or not to serialise the object after redacting. Typical use cases for this are when you want to send it over the network or save to a file, both of which are common use cases for redacting sensitive information. | boolean | |
|
|
93
|
-
| serialize | Alias of `serialise` for International-English users. | boolean | |
|
|
115
|
+
| serialise | Determines whether or not to serialise the object after redacting. Typical use cases for this are when you want to send it over the network or save to a file, both of which are common use cases for redacting sensitive information. | boolean | | true | N |
|
|
116
|
+
| serialize | Alias of `serialise` for International-English users. | boolean | | Value of `serialise` | N |
|
|
117
|
+
| transformers | A list of transformers to apply when transforming unsupported values. Each transformer should conditionally transform the value, or return the value unchanged to be passed to the next transformer. Transformers will be run in the order they are provided over the entire object. | array | Array<Transformer> | All standard transformers (bigint, date, error, map, regex, set, and url) | N |
|
|
94
118
|
|
|
95
119
|
### BlacklistKeyConfig
|
|
96
120
|
|
|
@@ -100,6 +124,8 @@ strRedaction.redact('<email>someone@somewhere.com</email><keepThis>This is fine<
|
|
|
100
124
|
| fuzzyKeyMatch | boolean | Main options `fuzzyKeyMatch` | N |
|
|
101
125
|
| caseSensitiveKeyMatch | boolean | Main options `caseSensitiveKeyMatch` | N |
|
|
102
126
|
| remove | boolean | Main options `remove` | N |
|
|
127
|
+
| replacement | string│function | Main options `replacement` | N |
|
|
128
|
+
| replaceStringByLength | boolean | Main options `replaceStringByLength` | N |
|
|
103
129
|
| retainStructure | boolean | Main options `retainStructure` | N |
|
|
104
130
|
|
|
105
131
|
### StringTestConfig
|
|
@@ -108,49 +134,3 @@ strRedaction.redact('<email>someone@somewhere.com</email><keepThis>This is fine<
|
|
|
108
134
|
| --- | --- | --- | --- |
|
|
109
135
|
| pattern | A regular expression to perform against a string value, whether that value is a flat string or nested within an object. | RegExp | Y |
|
|
110
136
|
| replacer | A function that will be called with the value of the string that matched the pattern and the pattern itself. This function should return the new (redacted) value to replace the original value. | function | Y |
|
|
111
|
-
|
|
112
|
-
### Benchmark
|
|
113
|
-
Comparisons are made against JSON.stringify, Regex.replace, Fast Redact &
|
|
114
|
-
(one of my other creations, [@hackylabs/obglob](https://npmjs.com/package/@hackylabs/obglob)) as well as different
|
|
115
|
-
configurations of Deep Redact, using [this test object](./test/setup/dummyUser.ts). Fast Redact was configured to redact
|
|
116
|
-
the same keys on the same object as Deep Redact without using wildcards.
|
|
117
|
-
|
|
118
|
-
The benchmark is run on a 2021 iMac with an M1 chip with 16GB memory running macOS Sequoia 15.0.0.
|
|
119
|
-
|
|
120
|
-
JSON.stringify is included as a benchmark because it is the fastest way to deeply iterate over an object, although it
|
|
121
|
-
doesn't redact any sensitive information.
|
|
122
|
-
|
|
123
|
-
Regex.replace is included as a benchmark because it is the fastest way to redact sensitive information from a string.
|
|
124
|
-
However, a regex pattern for all keys to be redacted is much harder to configure than a dedicated redaction library,
|
|
125
|
-
especially when dealing with multiple types of values. It also doesn't handle circular references or other unsupported
|
|
126
|
-
values as gracefully as deep-redact unless a third-party library is used to stringify the object beforehand.
|
|
127
|
-
|
|
128
|
-
Fast-redact is included as a benchmark because it's the next fastest library available specifically for redaction.
|
|
129
|
-
|
|
130
|
-
Neither JSON.stringify, Regex.replace nor Fast Redact offer the same level of configurability as deep-redact. Both Fast
|
|
131
|
-
Redact and Obglob are slower and rely on dependencies.
|
|
132
|
-
|
|
133
|
-

|
|
134
|
-
|
|
135
|
-
| scenario | ops / sec | op duration (ms) | margin of error | sample count |
|
|
136
|
-
| --- | --- | --- | --- | --- |
|
|
137
|
-
| DeepRedact, partial redaction | 178183.03 | 0.0056122067 | 0.00003 | 89092 |
|
|
138
|
-
| JSON.stringify, large object | 161672.63 | 0.0061853388 | 0.00004 | 80837 |
|
|
139
|
-
| DeepRedact, remove item, single object | 25085.95 | 0.039862959 | 0.00033 | 12543 |
|
|
140
|
-
| DeepRedact, default config, large object | 23164.57 | 0.0431693713 | 0.00023 | 11583 |
|
|
141
|
-
| Regex replace, large object | 22828.32 | 0.043805244 | 0.00031 | 11415 |
|
|
142
|
-
| DeepRedact, custom replacer function, single object | 22520.52 | 0.0444039553 | 0.00039 | 11261 |
|
|
143
|
-
| DeepRedact, replace string by length, single object | 22470.94 | 0.0445019191 | 0.00025 | 11236 |
|
|
144
|
-
| DeepRedact, retain structure, single object | 18315.67 | 0.0545980591 | 0.00026 | 9158 |
|
|
145
|
-
| DeepRedact, fuzzy matching, single object | 18077.45 | 0.0553175285 | 0.00035 | 9039 |
|
|
146
|
-
| DeepRedact, config per key, single object | 16055.96 | 0.0622821745 | 0.00031 | 8028 |
|
|
147
|
-
| DeepRedact, default config, 1000 large objects | 8077.4 | 0.1238022 | 0.00187 | 4039 |
|
|
148
|
-
| fast redact, large object | 5918.61 | 0.1689584628 | 0.00151 | 2960 |
|
|
149
|
-
| ObGlob, large object | 5193.29 | 0.1925563273 | 0.00972 | 2597 |
|
|
150
|
-
| DeepRedact, case insensitive matching, single object | 3477.6 | 0.2875549482 | 0.00324 | 1739 |
|
|
151
|
-
| DeepRedact, fuzzy and case insensitive matching, single object | 3437.73 | 0.2908896131 | 0.00239 | 1719 |
|
|
152
|
-
| ObGlob, 1000 large objects | 237.69 | 4.2072005126 | 0.05005 | 119 |
|
|
153
|
-
| JSON.stringify, 1000 large objects | 230.33 | 4.3416907672 | 0.04373 | 116 |
|
|
154
|
-
| DeepRedact, partial redaction large string | 127.18 | 7.8628400156 | 0.4176 | 64 |
|
|
155
|
-
| fast redact, 1000 large objects | 119.33 | 8.3803393 | 0.31943 | 60 |
|
|
156
|
-
| Regex replace, 1000 large objects | 97.12 | 10.2963537755 | 0.29341 | 49 |
|
package/dist/cjs/index.js
CHANGED
|
@@ -14,8 +14,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.
|
|
18
|
-
const
|
|
17
|
+
exports.default = exports.DeepRedact = void 0;
|
|
18
|
+
const utils_1 = __importDefault(require("./utils"));
|
|
19
19
|
class DeepRedact {
|
|
20
20
|
/**
|
|
21
21
|
* Create a new DeepRedact instance with the provided configuration.
|
|
@@ -24,11 +24,6 @@ class DeepRedact {
|
|
|
24
24
|
* @param {DeepRedactConfig} config. The configuration for the redaction.
|
|
25
25
|
*/
|
|
26
26
|
constructor(config) {
|
|
27
|
-
/**
|
|
28
|
-
* A WeakSet to store circular references during redaction. Reset to null after redaction is complete.
|
|
29
|
-
* @private
|
|
30
|
-
*/
|
|
31
|
-
this.circularReference = null;
|
|
32
27
|
/**
|
|
33
28
|
* The configuration for the redaction.
|
|
34
29
|
* @private
|
|
@@ -36,138 +31,22 @@ class DeepRedact {
|
|
|
36
31
|
this.config = {
|
|
37
32
|
serialise: false,
|
|
38
33
|
};
|
|
39
|
-
/**
|
|
40
|
-
* A transformer for unsupported data types. If `serialise` is false, the value will be returned as is,
|
|
41
|
-
* otherwise it will transform the value into a format that is supported by JSON.stringify.
|
|
42
|
-
*
|
|
43
|
-
* Error, RegExp, and Date instances are technically supported by JSON.stringify,
|
|
44
|
-
* but they returned as empty objects, therefore they are also transformed here.
|
|
45
|
-
* @protected
|
|
46
|
-
* @param {unknown} value The value that is not supported by JSON.stringify.
|
|
47
|
-
* @returns {unknown} The value in a format that is supported by JSON.stringify.
|
|
48
|
-
*/
|
|
49
|
-
this.unsupportedTransformer = (value) => {
|
|
50
|
-
if (!this.config.serialise)
|
|
51
|
-
return value;
|
|
52
|
-
if (typeof value === 'bigint') {
|
|
53
|
-
return {
|
|
54
|
-
__unsupported: {
|
|
55
|
-
type: 'bigint',
|
|
56
|
-
value: value.toString(10),
|
|
57
|
-
radix: 10,
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
if (value instanceof Error) {
|
|
62
|
-
return {
|
|
63
|
-
__unsupported: {
|
|
64
|
-
type: 'error',
|
|
65
|
-
name: value.name,
|
|
66
|
-
message: value.message,
|
|
67
|
-
stack: value.stack,
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
if (value instanceof RegExp) {
|
|
72
|
-
return {
|
|
73
|
-
__unsupported: {
|
|
74
|
-
type: 'regexp',
|
|
75
|
-
source: value.source,
|
|
76
|
-
flags: value.flags,
|
|
77
|
-
},
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
if (value instanceof Set) {
|
|
81
|
-
return {
|
|
82
|
-
__unsupported: {
|
|
83
|
-
type: 'set',
|
|
84
|
-
values: Array.from(value),
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
if (value instanceof Map) {
|
|
89
|
-
return {
|
|
90
|
-
__unsupported: {
|
|
91
|
-
type: 'map',
|
|
92
|
-
entries: Object.fromEntries(value.entries()),
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
if (value instanceof URL)
|
|
97
|
-
return value.toString();
|
|
98
|
-
if (value instanceof Date)
|
|
99
|
-
return value.toISOString();
|
|
100
|
-
return value;
|
|
101
|
-
};
|
|
102
|
-
/**
|
|
103
|
-
* Calls `unsupportedTransformer` on the provided value and rewrites any circular references.
|
|
104
|
-
*
|
|
105
|
-
* Circular references will always be removed to avoid infinite recursion.
|
|
106
|
-
* When a circular reference is found, the value will be replaced with `[[CIRCULAR_REFERENCE: path.to.original.value]]`.
|
|
107
|
-
* @protected
|
|
108
|
-
* @param {unknown} value The value to rewrite.
|
|
109
|
-
* @param {string | undefined} path The path to the value in the object.
|
|
110
|
-
* @returns {unknown} The rewritten value.
|
|
111
|
-
*/
|
|
112
|
-
this.rewriteUnsupported = (value, path) => {
|
|
113
|
-
const safeValue = this.unsupportedTransformer(value);
|
|
114
|
-
if (!(safeValue instanceof Object))
|
|
115
|
-
return safeValue;
|
|
116
|
-
if (this.circularReference === null)
|
|
117
|
-
this.circularReference = new WeakSet();
|
|
118
|
-
if (Array.isArray(safeValue)) {
|
|
119
|
-
return safeValue.map((val, index) => {
|
|
120
|
-
var _a, _b;
|
|
121
|
-
const newPath = path ? `${path}.[${index}]` : `[${index}]`;
|
|
122
|
-
if ((_a = this.circularReference) === null || _a === void 0 ? void 0 : _a.has(val))
|
|
123
|
-
return `[[CIRCULAR_REFERENCE: ${newPath}]]`;
|
|
124
|
-
if (val instanceof Object) {
|
|
125
|
-
(_b = this.circularReference) === null || _b === void 0 ? void 0 : _b.add(val);
|
|
126
|
-
return this.rewriteUnsupported(val, newPath);
|
|
127
|
-
}
|
|
128
|
-
return val;
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
return Object.fromEntries(Object.entries(safeValue).map(([key, val]) => {
|
|
132
|
-
var _a, _b;
|
|
133
|
-
const newPath = path ? `${path}.${key}` : key;
|
|
134
|
-
if ((_a = this.circularReference) === null || _a === void 0 ? void 0 : _a.has(val))
|
|
135
|
-
return [key, `[[CIRCULAR_REFERENCE: ${newPath}]]`];
|
|
136
|
-
if (val instanceof Object)
|
|
137
|
-
(_b = this.circularReference) === null || _b === void 0 ? void 0 : _b.add(val);
|
|
138
|
-
return [key, this.rewriteUnsupported(val, path ? `${path}.${key}` : key)];
|
|
139
|
-
}));
|
|
140
|
-
};
|
|
141
|
-
/**
|
|
142
|
-
* Depending on the value of `serialise`, return the value as a JSON string or as the provided value.
|
|
143
|
-
*
|
|
144
|
-
* Also resets the `circularReference` property to null after redaction is complete.
|
|
145
|
-
* This is to ensure that the WeakSet doesn't cause memory leaks.
|
|
146
|
-
* @private
|
|
147
|
-
* @param value
|
|
148
|
-
*/
|
|
149
|
-
this.maybeSerialise = (value) => {
|
|
150
|
-
this.circularReference = null;
|
|
151
|
-
const result = this.redactorUtils.partialStringRedact(value);
|
|
152
|
-
if (!this.config.serialise)
|
|
153
|
-
return result;
|
|
154
|
-
return typeof result === 'string' ? result : JSON.stringify(result);
|
|
155
|
-
};
|
|
156
34
|
/**
|
|
157
35
|
* Redact the provided value. The value will be stripped of any circular references and other unsupported data types, before being redacted according to the configuration and finally serialised if required.
|
|
158
36
|
* @param {unknown} value The value to redact.
|
|
159
37
|
* @returns {unknown} The redacted value.
|
|
38
|
+
* @throws {Error} If the value cannot be serialised to JSON and serialise is true.
|
|
160
39
|
*/
|
|
161
40
|
this.redact = (value) => {
|
|
162
|
-
|
|
41
|
+
const redacted = this.redactorUtils.traverse(value);
|
|
42
|
+
return this.config.serialise ? JSON.stringify(redacted) : redacted;
|
|
163
43
|
};
|
|
164
44
|
const { serialise, serialize } = config, rest = __rest(config, ["serialise", "serialize"]);
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
this.config.serialise =
|
|
168
|
-
|
|
169
|
-
this.config.serialise = serialize;
|
|
45
|
+
const englishSerialise = serialise !== null && serialise !== void 0 ? serialise : serialize;
|
|
46
|
+
if (typeof englishSerialise === 'boolean')
|
|
47
|
+
this.config.serialise = englishSerialise;
|
|
48
|
+
this.redactorUtils = new utils_1.default(Object.assign({}, rest));
|
|
170
49
|
}
|
|
171
50
|
}
|
|
172
|
-
exports.default = DeepRedact;
|
|
173
51
|
exports.DeepRedact = DeepRedact;
|
|
52
|
+
exports.default = DeepRedact;
|