@hackylabs/deep-redact 2.0.2 → 2.1.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/README.md CHANGED
@@ -3,13 +3,13 @@
3
3
  [![npm version](https://badge.fury.io/js/@hackylabs%2Fdeep-redact.svg)](https://badge.fury.io/js/@hackylabs%2Fdeep-redact)
4
4
  [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/hackylabs/deep-redact/blob/main/LICENSE)
5
5
 
6
- Faster than Fast Redact <sup>1</sup> as well as being safer and more configurable than many other redaction libraries,
6
+ Faster than Fast Redact <sup>1</sup> as well as being safer and more configurable than many other redaction solutions,
7
7
  Deep Redact is a zero-dependency tool that redacts sensitive information from strings and objects. It is designed to be
8
8
  used in a production environment where sensitive information needs to be redacted from logs, error messages, files,
9
9
  and other outputs.
10
10
 
11
11
  Circular references and other unsupported values are handled gracefully, and the library is designed to be as fast as
12
- possible while still being configurable.
12
+ possible while still being easy to use and configure.
13
13
 
14
14
  Supporting both CommonJS and ESM, with named and default exports, Deep Redact is designed to be versatile and easy to
15
15
  use in any modern JavaScript or TypeScript project in Node or the browser.
@@ -31,9 +31,8 @@ library outside of your global logging/error-reporting libraries.</h4>
31
31
  // ./src/example.ts
32
32
  import {DeepRedact} from '@hackylabs/deep-redact'; // If you're using CommonJS, import with require('@hackylabs/deep-redact') instead. Both CommonJS and ESM support named and default imports.
33
33
 
34
- const redaction = new DeepRedact({
34
+ const objRedaction = new DeepRedact({
35
35
  blacklistedKeys: ['sensitive', 'password', /name/i],
36
- serialise: false,
37
36
  })
38
37
 
39
38
  const obj = {
@@ -46,7 +45,8 @@ const obj = {
46
45
  }
47
46
  }
48
47
 
49
- redaction.redact(obj)
48
+ // Recursively redact sensitive information from an object
49
+ objRedaction.redact(obj)
50
50
  // {
51
51
  // keepThis: 'This is fine',
52
52
  // sensitive: '[REDACTED]',
@@ -56,6 +56,19 @@ redaction.redact(obj)
56
56
  // firstName: '[REDACTED]'
57
57
  // }
58
58
  // }
59
+
60
+ const strRedaction = new DeepRedact({
61
+ stringTests: [
62
+ {
63
+ pattern: /<(email|password)>([^<]+)<\/\1>/gi,
64
+ replacer: (value: string, pattern: RegExp) => value.replace(pattern, '<$1>[REDACTED]</$1>'),
65
+ },
66
+ ],
67
+ })
68
+
69
+ // Partially redact sensitive information from a string
70
+ strRedaction.redact('<email>someone@somewhere.com</email><keepThis>This is fine</keepThis><password>secret</password>')
71
+ // '<email>[REDACTED]</email><keepThis>This is fine</keepThis><password>[REDACTED]</password>'
59
72
  ```
60
73
 
61
74
  ## Configuration
@@ -65,7 +78,7 @@ redaction.redact(obj)
65
78
  | key | description | type | options | default | required |
66
79
  | --- | --- | --- | --- | --- | --- |
67
80
  | blacklistedKeys | Deeply compare names of these keys against the keys in your object. | array | Array<string│RegExp│BlacklistKeyConfig> | [] | N |
68
- | stringTests | Array of regular expressions to perform against string values, whether that value is a flat string or nested within an object. | array | RegExp[] | [] | N |
81
+ | stringTests | Array of regular expressions to perform against string values, whether that value is a flat string or nested within an object. | array | Array<RegExp│StringTestConfig> | [] | N |
69
82
  | 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 |
70
83
  | 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 |
71
84
  | remove | Determines whether or not to remove the key from the object when it is redacted. | boolean | | false | N |
@@ -86,6 +99,13 @@ redaction.redact(obj)
86
99
  | remove | boolean | Main options `remove` | N |
87
100
  | retainStructure | boolean | Main options `retainStructure` | N |
88
101
 
102
+ ### StringTestConfig
103
+
104
+ | key | description | type | required |
105
+ | --- | --- | --- | --- |
106
+ | pattern | A regular expression to perform against a string value, whether that value is a flat string or nested within an object. | RegExp | Y |
107
+ | 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 |
108
+
89
109
  ### Benchmark
90
110
  Comparisons are made against JSON.stringify, Regex.replace, Fast Redact &
91
111
  (one of my other creations, [@hackylabs/obglob](https://npmjs.com/package/@hackylabs/obglob)) as well as different
@@ -111,21 +131,22 @@ Redact and Obglob are slower and rely on dependencies.
111
131
 
112
132
  | scenario | ops / sec | op duration (ms) | margin of error | sample count |
113
133
  | --- | --- | --- | --- | --- |
114
- | JSON.stringify, large object | 161827.79 | 0.0061794083 | 0.00002 | 80914 |
115
- | DeepRedact, remove item, single object | 26010.46 | 0.0384460656 | 0.00016 | 13006 |
116
- | DeepRedact, custom replacer function, single object | 22412.54 | 0.0446178767 | 0.00031 | 11207 |
117
- | DeepRedact, replace string by length, single object | 22323.79 | 0.044795253 | 0.00024 | 11162 |
118
- | DeepRedact, default config, large object | 21932.77 | 0.0455938725 | 0.00025 | 10967 |
119
- | Regex replace, large object | 21919.75 | 0.0456209497 | 0.00027 | 10960 |
120
- | DeepRedact, retain structure, single object | 18417.65 | 0.0542957469 | 0.00024 | 9212 |
121
- | DeepRedact, fuzzy matching, single object | 17428.25 | 0.0573781129 | 0.00028 | 8715 |
122
- | DeepRedact, config per key, single object | 16975.98 | 0.0589067685 | 0.00033 | 8488 |
123
- | DeepRedact, default config, 1000 large objects | 7787.76 | 0.1284065968 | 0.00319 | 3894 |
124
- | fast redact, large object | 5847.55 | 0.1710116908 | 0.00143 | 2924 |
125
- | DeepRedact, case insensitive matching, single object | 5136.64 | 0.1946798809 | 0.00152 | 2569 |
126
- | ObGlob, large object | 5083.79 | 0.1967037628 | 0.01079 | 2542 |
127
- | DeepRedact, fuzzy and case insensitive matching, single object | 4819.04 | 0.2075101033 | 0.00142 | 2410 |
128
- | JSON.stringify, 1000 large objects | 226.71 | 4.4109355351 | 0.04147 | 114 |
129
- | ObGlob, 1000 large objects | 164.41 | 6.0825151928 | 0.15338 | 83 |
130
- | fast redact, 1000 large objects | 121.82 | 8.2088192787 | 0.09619 | 61 |
131
- | Regex replace, 1000 large objects | 94.57 | 10.5740055833 | 0.30159 | 48 |
134
+ | DeepRedact, XML | 171985.01 | 0.0058144601 | 0.00004 | 85993 |
135
+ | JSON.stringify, large object | 162406.59 | 0.0061573855 | 0.00002 | 81204 |
136
+ | DeepRedact, remove item, single object | 25293.55 | 0.039535775 | 0.00023 | 12647 |
137
+ | Regex replace, large object | 22529.52 | 0.0443862153 | 0.00024 | 11265 |
138
+ | DeepRedact, custom replacer function, single object | 22324.84 | 0.0447931554 | 0.00037 | 11163 |
139
+ | DeepRedact, default config, large object | 21437.04 | 0.046648239 | 0.00028 | 10719 |
140
+ | DeepRedact, replace string by length, single object | 21018.32 | 0.0475775519 | 0.00084 | 10510 |
141
+ | DeepRedact, fuzzy matching, single object | 18000.38 | 0.0555543858 | 0.0003 | 9001 |
142
+ | DeepRedact, retain structure, single object | 17581.5 | 0.056877956 | 0.00037 | 8791 |
143
+ | DeepRedact, config per key, single object | 16914.73 | 0.0591200552 | 0.0004 | 8458 |
144
+ | DeepRedact, default config, 1000 large objects | 7989.05 | 0.1251712531 | 0.0018 | 3995 |
145
+ | fast redact, large object | 5929.58 | 0.1686459096 | 0.00126 | 2965 |
146
+ | ObGlob, large object | 4939.98 | 0.2024300664 | 0.01105 | 2470 |
147
+ | DeepRedact, case insensitive matching, single object | 4721.59 | 0.2117932541 | 0.00378 | 2361 |
148
+ | DeepRedact, fuzzy and case insensitive matching, single object | 4686.29 | 0.2133882948 | 0.0018 | 2344 |
149
+ | JSON.stringify, 1000 large objects | 222.05 | 4.5035912679 | 0.04553 | 112 |
150
+ | ObGlob, 1000 large objects | 164.55 | 6.0771323614 | 0.11829 | 83 |
151
+ | fast redact, 1000 large objects | 120.7 | 8.2848202295 | 0.05702 | 61 |
152
+ | Regex replace, 1000 large objects | 93.5 | 10.6954525319 | 0.39014 | 47 |
package/dist/cjs/index.js CHANGED
@@ -77,6 +77,24 @@ class DeepRedact {
77
77
  },
78
78
  };
79
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();
80
98
  if (value instanceof Date)
81
99
  return value.toISOString();
82
100
  return value;
@@ -80,11 +80,32 @@ class RedactorUtils {
80
80
  * @param shouldRedact
81
81
  */
82
82
  this.redactString = (value, replacement, remove, shouldRedact) => {
83
- if (!value)
83
+ if (!value || typeof value !== 'string')
84
84
  return value;
85
85
  const { stringTests } = this.config;
86
- if (!shouldRedact && !(stringTests === null || stringTests === void 0 ? void 0 : stringTests.some((pattern) => pattern.test(value))))
86
+ if (!shouldRedact) {
87
+ const result = stringTests === null || stringTests === void 0 ? void 0 : stringTests.map((test) => {
88
+ if (test instanceof RegExp) {
89
+ if (!test.test(value))
90
+ return value;
91
+ if (remove)
92
+ return undefined;
93
+ if (typeof replacement === 'function')
94
+ return replacement(value);
95
+ if (this.config.replaceStringByLength)
96
+ return replacement.repeat(value.length);
97
+ return replacement;
98
+ }
99
+ if (remove && test.pattern.test(value))
100
+ return undefined;
101
+ return test.replacer(value, test.pattern);
102
+ }).filter(Boolean)[0];
103
+ if (result)
104
+ return result;
105
+ if (remove)
106
+ return undefined;
87
107
  return value;
108
+ }
88
109
  if (remove)
89
110
  return undefined;
90
111
  if (typeof replacement === 'function')
@@ -72,6 +72,24 @@ class DeepRedact {
72
72
  },
73
73
  };
74
74
  }
75
+ if (value instanceof Set) {
76
+ return {
77
+ __unsupported: {
78
+ type: 'set',
79
+ values: Array.from(value),
80
+ },
81
+ };
82
+ }
83
+ if (value instanceof Map) {
84
+ return {
85
+ __unsupported: {
86
+ type: 'map',
87
+ entries: Object.fromEntries(value.entries()),
88
+ },
89
+ };
90
+ }
91
+ if (value instanceof URL)
92
+ return value.toString();
75
93
  if (value instanceof Date)
76
94
  return value.toISOString();
77
95
  return value;
@@ -131,11 +131,32 @@ class RedactorUtils {
131
131
  * @param shouldRedact
132
132
  */
133
133
  redactString = (value, replacement, remove, shouldRedact) => {
134
- if (!value)
134
+ if (!value || typeof value !== 'string')
135
135
  return value;
136
136
  const { stringTests } = this.config;
137
- if (!shouldRedact && !stringTests?.some((pattern) => pattern.test(value)))
137
+ if (!shouldRedact) {
138
+ const result = stringTests?.map((test) => {
139
+ if (test instanceof RegExp) {
140
+ if (!test.test(value))
141
+ return value;
142
+ if (remove)
143
+ return undefined;
144
+ if (typeof replacement === 'function')
145
+ return replacement(value);
146
+ if (this.config.replaceStringByLength)
147
+ return replacement.repeat(value.length);
148
+ return replacement;
149
+ }
150
+ if (remove && test.pattern.test(value))
151
+ return undefined;
152
+ return test.replacer(value, test.pattern);
153
+ }).filter(Boolean)[0];
154
+ if (result)
155
+ return result;
156
+ if (remove)
157
+ return undefined;
138
158
  return value;
159
+ }
139
160
  if (remove)
140
161
  return undefined;
141
162
  if (typeof replacement === 'function')
@@ -58,7 +58,10 @@ export interface BaseDeepRedactConfig {
58
58
  * /^[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}$/, // redact any string that looks like an IP address.
59
59
  * ]
60
60
  */
61
- stringTests?: RegExp[];
61
+ stringTests?: Array<RegExp | {
62
+ pattern: RegExp;
63
+ replacer: (value: string, pattern: RegExp) => string;
64
+ }>;
62
65
  /**
63
66
  * Perform a fuzzy match on the key. This will match any key that contains the string, rather than a case-sensitive match.
64
67
  * @default false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hackylabs/deep-redact",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "A fast, safe and configurable zero-dependency library for redacting strings or deeply redacting arrays and objects.",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -25,6 +25,10 @@
25
25
  "dist"
26
26
  ],
27
27
  "keywords": [
28
+ "json",
29
+ "xml",
30
+ "document",
31
+ "fast",
28
32
  "redact",
29
33
  "redaction",
30
34
  "redactor",