@hackylabs/deep-redact 2.2.0 → 2.2.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Benjamin Green (https://bengreen.dev)
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
@@ -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
@@ -134,23 +158,23 @@ Redact and Obglob are slower and rely on dependencies.
134
158
 
135
159
  | scenario | ops / sec | op duration (ms) | margin of error | sample count |
136
160
  | --- | --- | --- | --- | --- |
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 |
161
+ | DeepRedact, partial redaction | 176654.38 | 0.0056607711 | 0.00003 | 88329 |
162
+ | JSON.stringify, large object | 164287.01 | 0.0060869085 | 0.00002 | 82144 |
163
+ | DeepRedact, remove item, single object | 25142.69 | 0.0397729959 | 0.00029 | 12572 |
164
+ | Regex replace, large object | 23061.11 | 0.0433630529 | 0.00022 | 11531 |
165
+ | DeepRedact, default config, large object | 21454.71 | 0.0466098038 | 0.00086 | 10728 |
166
+ | DeepRedact, custom replacer function, single object | 21026.51 | 0.047559016 | 0.00047 | 10514 |
167
+ | DeepRedact, replace string by length, single object | 19629.37 | 0.0509440788 | 0.00032 | 9815 |
168
+ | DeepRedact, retain structure, single object | 18238.97 | 0.0548276723 | 0.00049 | 9120 |
169
+ | DeepRedact, fuzzy matching, single object | 17470.6 | 0.0572390237 | 0.00029 | 8736 |
170
+ | DeepRedact, config per key, single object | 15398.94 | 0.0649395488 | 0.00036 | 7700 |
171
+ | DeepRedact, default config, 1000 large objects | 8401.8 | 0.1190220507 | 0.00103 | 4201 |
172
+ | fast redact, large object | 5898.84 | 0.1695249305 | 0.00133 | 2950 |
173
+ | ObGlob, large object | 4876.54 | 0.2050635404 | 0.01142 | 2439 |
174
+ | DeepRedact, case insensitive matching, single object | 3576.62 | 0.279593299 | 0.00282 | 1789 |
175
+ | DeepRedact, fuzzy and case insensitive matching, single object | 3379.78 | 0.295877197 | 0.00244 | 1690 |
176
+ | JSON.stringify, 1000 large objects | 220.76 | 4.5298012342 | 0.10929 | 111 |
177
+ | ObGlob, 1000 large objects | 166.2 | 6.0168303571 | 0.07621 | 84 |
178
+ | DeepRedact, partial redaction large string | 126.88 | 7.8814680469 | 0.28048 | 64 |
179
+ | fast redact, 1000 large objects | 122.12 | 8.1884899032 | 0.06661 | 62 |
180
+ | Regex replace, 1000 large objects | 93.88 | 10.6515390208 | 0.36668 | 48 |
package/dist/cjs/index.js CHANGED
@@ -47,8 +47,6 @@ class DeepRedact {
47
47
  * @returns {unknown} The value in a format that is supported by JSON.stringify.
48
48
  */
49
49
  this.unsupportedTransformer = (value) => {
50
- if (!this.config.serialise)
51
- return value;
52
50
  if (typeof value === 'bigint') {
53
51
  return {
54
52
  __unsupported: {
@@ -145,18 +143,27 @@ class DeepRedact {
145
143
  * This is to ensure that the WeakSet doesn't cause memory leaks.
146
144
  * @private
147
145
  * @param value
146
+ * @returns {unknown} The value as a JSON string or as the provided value.
147
+ * @throws {Error} If the value cannot be serialised.
148
148
  */
149
149
  this.maybeSerialise = (value) => {
150
150
  this.circularReference = null;
151
- const result = this.redactorUtils.partialStringRedact(value);
152
151
  if (!this.config.serialise)
153
- return result;
154
- return typeof result === 'string' ? result : JSON.stringify(result);
152
+ return value;
153
+ if (typeof value === 'string')
154
+ return value;
155
+ try {
156
+ return JSON.stringify(value);
157
+ }
158
+ catch (error) {
159
+ throw new Error('Failed to serialise value. Did you override the `unsupportedTransformer` method and return a value that is not supported by JSON.stringify?');
160
+ }
155
161
  };
156
162
  /**
157
163
  * 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
164
  * @param {unknown} value The value to redact.
159
165
  * @returns {unknown} The redacted value.
166
+ * @throws {Error} If the value cannot be serialised.
160
167
  */
161
168
  this.redact = (value) => {
162
169
  return this.maybeSerialise(this.redactorUtils.recurse(this.rewriteUnsupported(value)));
@@ -83,36 +83,37 @@ class RedactorUtils {
83
83
  this.redactString = (value, replacement, remove, shouldRedact) => {
84
84
  if (!value || typeof value !== 'string')
85
85
  return value;
86
+ const maybePartiallyRedacted = this.partialStringRedact(value);
86
87
  const { stringTests } = this.config;
87
88
  if (!shouldRedact) {
88
89
  const result = stringTests === null || stringTests === void 0 ? void 0 : stringTests.map((test) => {
89
90
  if (test instanceof RegExp) {
90
- if (!test.test(value))
91
- return value;
91
+ if (!test.test(maybePartiallyRedacted))
92
+ return maybePartiallyRedacted;
92
93
  if (remove)
93
94
  return undefined;
94
95
  if (typeof replacement === 'function')
95
- return replacement(value);
96
+ return replacement(maybePartiallyRedacted);
96
97
  if (this.config.replaceStringByLength)
97
- return replacement.repeat(value.length);
98
+ return replacement.repeat(maybePartiallyRedacted.length);
98
99
  return replacement;
99
100
  }
100
- if (remove && test.pattern.test(value))
101
+ if (remove && test.pattern.test(maybePartiallyRedacted))
101
102
  return undefined;
102
- return test.replacer(value, test.pattern);
103
+ return test.replacer(maybePartiallyRedacted, test.pattern);
103
104
  }).filter(Boolean)[0];
104
105
  if (result)
105
106
  return result;
106
107
  if (remove)
107
108
  return undefined;
108
- return value;
109
+ return maybePartiallyRedacted;
109
110
  }
110
111
  if (remove)
111
112
  return undefined;
112
113
  if (typeof replacement === 'function')
113
- return replacement(value);
114
+ return replacement(maybePartiallyRedacted);
114
115
  if (this.config.replaceStringByLength)
115
- return replacement.repeat(value.length);
116
+ return replacement.repeat(maybePartiallyRedacted.length);
116
117
  return replacement;
117
118
  };
118
119
  /**
@@ -165,23 +166,11 @@ class RedactorUtils {
165
166
  const { partialStringTests } = this.config;
166
167
  if (partialStringTests.length === 0)
167
168
  return value;
168
- let result;
169
- if (typeof value === 'string') {
170
- result = value;
171
- }
172
- else {
173
- try {
174
- result = JSON.stringify(value);
175
- }
176
- catch (error) {
177
- // It should never reach this point, but if it does, it will throw an error that must not contain sensitive data.
178
- throw new Error('Failed to stringify value for partialStringRedact. Did you replace the rewriteUnsupported method with something that returns non-serialisable data?');
179
- }
180
- }
169
+ let result = value;
181
170
  partialStringTests.forEach((test) => {
182
171
  result = test.replacer(result, test.pattern);
183
172
  });
184
- return typeof value === 'string' ? result : JSON.parse(result);
173
+ return result;
185
174
  };
186
175
  /**
187
176
  * Redact a value. If the value is an object or array, the redaction will be performed recursively, otherwise the value will be redacted if it is a supported type using the `replace` method.
@@ -42,8 +42,6 @@ class DeepRedact {
42
42
  * @returns {unknown} The value in a format that is supported by JSON.stringify.
43
43
  */
44
44
  unsupportedTransformer = (value) => {
45
- if (!this.config.serialise)
46
- return value;
47
45
  if (typeof value === 'bigint') {
48
46
  return {
49
47
  __unsupported: {
@@ -138,18 +136,27 @@ class DeepRedact {
138
136
  * This is to ensure that the WeakSet doesn't cause memory leaks.
139
137
  * @private
140
138
  * @param value
139
+ * @returns {unknown} The value as a JSON string or as the provided value.
140
+ * @throws {Error} If the value cannot be serialised.
141
141
  */
142
142
  maybeSerialise = (value) => {
143
143
  this.circularReference = null;
144
- const result = this.redactorUtils.partialStringRedact(value);
145
144
  if (!this.config.serialise)
146
- return result;
147
- return typeof result === 'string' ? result : JSON.stringify(result);
145
+ return value;
146
+ if (typeof value === 'string')
147
+ return value;
148
+ try {
149
+ return JSON.stringify(value);
150
+ }
151
+ catch (error) {
152
+ throw new Error('Failed to serialise value. Did you override the `unsupportedTransformer` method and return a value that is not supported by JSON.stringify?');
153
+ }
148
154
  };
149
155
  /**
150
156
  * 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.
151
157
  * @param {unknown} value The value to redact.
152
158
  * @returns {unknown} The redacted value.
159
+ * @throws {Error} If the value cannot be serialised.
153
160
  */
154
161
  redact = (value) => {
155
162
  return this.maybeSerialise(this.redactorUtils.recurse(this.rewriteUnsupported(value)));
@@ -135,36 +135,37 @@ class RedactorUtils {
135
135
  redactString = (value, replacement, remove, shouldRedact) => {
136
136
  if (!value || typeof value !== 'string')
137
137
  return value;
138
+ const maybePartiallyRedacted = this.partialStringRedact(value);
138
139
  const { stringTests } = this.config;
139
140
  if (!shouldRedact) {
140
141
  const result = stringTests?.map((test) => {
141
142
  if (test instanceof RegExp) {
142
- if (!test.test(value))
143
- return value;
143
+ if (!test.test(maybePartiallyRedacted))
144
+ return maybePartiallyRedacted;
144
145
  if (remove)
145
146
  return undefined;
146
147
  if (typeof replacement === 'function')
147
- return replacement(value);
148
+ return replacement(maybePartiallyRedacted);
148
149
  if (this.config.replaceStringByLength)
149
- return replacement.repeat(value.length);
150
+ return replacement.repeat(maybePartiallyRedacted.length);
150
151
  return replacement;
151
152
  }
152
- if (remove && test.pattern.test(value))
153
+ if (remove && test.pattern.test(maybePartiallyRedacted))
153
154
  return undefined;
154
- return test.replacer(value, test.pattern);
155
+ return test.replacer(maybePartiallyRedacted, test.pattern);
155
156
  }).filter(Boolean)[0];
156
157
  if (result)
157
158
  return result;
158
159
  if (remove)
159
160
  return undefined;
160
- return value;
161
+ return maybePartiallyRedacted;
161
162
  }
162
163
  if (remove)
163
164
  return undefined;
164
165
  if (typeof replacement === 'function')
165
- return replacement(value);
166
+ return replacement(maybePartiallyRedacted);
166
167
  if (this.config.replaceStringByLength)
167
- return replacement.repeat(value.length);
168
+ return replacement.repeat(maybePartiallyRedacted.length);
168
169
  return replacement;
169
170
  };
170
171
  /**
@@ -217,23 +218,11 @@ class RedactorUtils {
217
218
  const { partialStringTests } = this.config;
218
219
  if (partialStringTests.length === 0)
219
220
  return value;
220
- let result;
221
- if (typeof value === 'string') {
222
- result = value;
223
- }
224
- else {
225
- try {
226
- result = JSON.stringify(value);
227
- }
228
- catch (error) {
229
- // It should never reach this point, but if it does, it will throw an error that must not contain sensitive data.
230
- throw new Error('Failed to stringify value for partialStringRedact. Did you replace the rewriteUnsupported method with something that returns non-serialisable data?');
231
- }
232
- }
221
+ let result = value;
233
222
  partialStringTests.forEach((test) => {
234
223
  result = test.replacer(result, test.pattern);
235
224
  });
236
- return typeof value === 'string' ? result : JSON.parse(result);
225
+ return result;
237
226
  };
238
227
  /**
239
228
  * Redact a value. If the value is an object or array, the redaction will be performed recursively, otherwise the value will be redacted if it is a supported type using the `replace` method.
@@ -51,12 +51,15 @@ declare class DeepRedact {
51
51
  * This is to ensure that the WeakSet doesn't cause memory leaks.
52
52
  * @private
53
53
  * @param value
54
+ * @returns {unknown} The value as a JSON string or as the provided value.
55
+ * @throws {Error} If the value cannot be serialised.
54
56
  */
55
57
  private maybeSerialise;
56
58
  /**
57
59
  * 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.
58
60
  * @param {unknown} value The value to redact.
59
61
  * @returns {unknown} The redacted value.
62
+ * @throws {Error} If the value cannot be serialised.
60
63
  */
61
64
  redact: (value: unknown) => unknown;
62
65
  }
@@ -77,7 +77,7 @@ declare class RedactorUtils {
77
77
  * @param {boolean} parentShouldRedact Whether the item should be redacted based on the key within the parent object.
78
78
  */
79
79
  private redactObject;
80
- partialStringRedact: (value: unknown) => unknown;
80
+ partialStringRedact: (value: string) => string;
81
81
  /**
82
82
  * Redact a value. If the value is an object or array, the redaction will be performed recursively, otherwise the value will be redacted if it is a supported type using the `replace` method.
83
83
  * @private
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hackylabs/deep-redact",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
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",