field-redactor 1.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/.nvmrc +1 -0
- package/CODEOWNERS +1 -0
- package/README.md +677 -0
- package/dist/customObjectChecker.d.ts +22 -0
- package/dist/customObjectChecker.d.ts.map +1 -0
- package/dist/customObjectChecker.js +72 -0
- package/dist/customObjectChecker.js.map +1 -0
- package/dist/errors.d.ts +20 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +35 -0
- package/dist/errors.js.map +1 -0
- package/dist/fieldRedactor.d.ts +25 -0
- package/dist/fieldRedactor.d.ts.map +1 -0
- package/dist/fieldRedactor.js +78 -0
- package/dist/fieldRedactor.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/objectRedactor.d.ts +42 -0
- package/dist/objectRedactor.d.ts.map +1 -0
- package/dist/objectRedactor.js +298 -0
- package/dist/objectRedactor.js.map +1 -0
- package/dist/primitiveRedactor.d.ts +28 -0
- package/dist/primitiveRedactor.d.ts.map +1 -0
- package/dist/primitiveRedactor.js +75 -0
- package/dist/primitiveRedactor.js.map +1 -0
- package/dist/secretManager.d.ts +32 -0
- package/dist/secretManager.d.ts.map +1 -0
- package/dist/secretManager.js +58 -0
- package/dist/secretManager.js.map +1 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +12 -0
- package/dist/types.js.map +1 -0
- package/jest.config.js +20 -0
- package/package.json +51 -0
- package/tsconfig.json +45 -0
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
v22.13.0
|
package/CODEOWNERS
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
* @mologna
|
package/README.md
ADDED
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
# FieldRedactor
|
|
2
|
+
|
|
3
|
+
A utility `npm` module for redacting sensitive data from JSON objects using complex, recursive evaluation logic.
|
|
4
|
+
|
|
5
|
+
It can redact JSON values based on Regular Expression matching of key values, custom object logic for PII values identified by fields other than their key, deeply redact sensitive objects, and other PII redaction strategies that more simplistic approaches cannot handle.
|
|
6
|
+
|
|
7
|
+
# History
|
|
8
|
+
In many instances, redaction of sensitive data from log output is fairly straightforward and can be accomplished through existing redaction libraries. However, I often encountered scenarios where such simplistic approaches were not sufficient. Take, for example, the following object:
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"name": "email",
|
|
13
|
+
"type": "String",
|
|
14
|
+
"value": "foo.bar@example.com"
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If this object is placed in a data set with many others, and only some of these objects contain PII, redaction becomes quite troublesome. One could redact all `value` fields from these objects - but this merely renders the log output useless for debugging or tracing, as all values are now redacted. The value of the `name` field determines whether or not the `value` field should be redacted. I often found myself writing custom logic to make this determination, which led to brittle code that was difficult to maintain. In the end I decided to write a new, highly configurable plug-and-play solution that could redact sensitive data in various manners while requiring only minor configuration tweaks. Enter FieldRedactor!
|
|
19
|
+
|
|
20
|
+
## Basic Usage
|
|
21
|
+
Basic usage of the FieldRedactor is straightforward but not recommended. Object redaction can be performed either in-place or return the redacted object:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { FieldRedactor } from 'field-redactor';
|
|
25
|
+
const myJsonObject = { foo: "bar", fizz: null };
|
|
26
|
+
const fieldRedactor = new FieldRedactor();
|
|
27
|
+
|
|
28
|
+
// return redacted result
|
|
29
|
+
const result = await fieldRedactor.redact(myJsonObject);
|
|
30
|
+
console.log(myJsonObject); // { foo: "bar", fizz: null }
|
|
31
|
+
console.log(result); // { foo: "REDACTED", fizz: null }
|
|
32
|
+
|
|
33
|
+
// redact in place
|
|
34
|
+
await fieldRedactor.redactInPlace(myJsonObject);
|
|
35
|
+
console.log(myJsonObject); // { foo: "REDACTED", fizz: null }
|
|
36
|
+
```
|
|
37
|
+
> **Note:** `null` and `undefined` values are not redacted by default.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Customization
|
|
42
|
+
The true power of this tool comes from its customization. FieldRedactor can be customized to redact only primtive values for certain keys, deeply redact others, while stringifying and fully redacting other keys. The redactor itself can be configured, and "Custom Objects" can be specified to handle certain object shapes in a highly specific and configurable way.
|
|
43
|
+
|
|
44
|
+
### Overview
|
|
45
|
+
| Config Field | Type | Default | Effect |
|
|
46
|
+
|-----------------------|---------------------------------|--------------------------------|-----------------------------------------------------------------------|
|
|
47
|
+
| `redactor` | `(val: any) => Promise<string>` | `(val) => Promise.resolve("REDACTED")` | The function to use when redacting values. |
|
|
48
|
+
| `secretKeys` | `RegExp[]` | `null` | Specifies which values at any level of the JSON object should be redacted. Objects are not deeply redacted, but primitive values in arrays are. If not specified, all values are considered secret. |
|
|
49
|
+
| `deepSecretKeys` | `RegExp[]` | `[]` | Specifies keys at any level of the JSON object to be deeply redacted. All values within matching objects are fully redacted unless matching a custom object. |
|
|
50
|
+
| `fullSecretKeys` | `RegExp[]` | `[]` | Specifies keys at any level of the JSON object to be stringified and fully redacted. Primarily used for objects and arrays. |
|
|
51
|
+
| `customObjects` | `CustomObject[]` | `[]` | Specifies custom objects requiring fine-tuned redaction logic, such as referencing sibling keys. See the "Custom Objects" section for details. |
|
|
52
|
+
| `ignoreBooleans` | `boolean` | `false` | If `true`, booleans will not be redacted even if secret. |
|
|
53
|
+
| `ignoreNullOrUndefined` | `boolean` | `true` | If `true`, `null` and `undefined` values will not be redacted. |
|
|
54
|
+
|
|
55
|
+
### `redactor` Configuration
|
|
56
|
+
Configures the redactor function used when a secret is encountered. Users should typically provide this configuration.
|
|
57
|
+
|
|
58
|
+
#### Details
|
|
59
|
+
- **Type:** `(val: any) => Promise<string>`
|
|
60
|
+
- **Effect:** The function used to redact values.
|
|
61
|
+
- Value can only be null or undefined if `ignoreNullOrUndefined` is set to false.
|
|
62
|
+
- Defaults to `() => Promise.resolve('REDACTED')`.
|
|
63
|
+
|
|
64
|
+
#### Example
|
|
65
|
+
##### Code
|
|
66
|
+
```typescript
|
|
67
|
+
import { FieldRedactor } from 'field-redactor';
|
|
68
|
+
import * as crypto from 'crypto';
|
|
69
|
+
const redactor: Redactor = (val: any) => Promise.resolve(crypto.createHash('sha256').update(val.toString()).digest('hex'));
|
|
70
|
+
const fieldRedactor = new FieldRedactor({
|
|
71
|
+
redactor
|
|
72
|
+
});
|
|
73
|
+
const result = await redactor.redact({foo: "bar"});
|
|
74
|
+
console.log(result);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
##### Output
|
|
78
|
+
```bash
|
|
79
|
+
'fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `secretKeys` Configuration
|
|
83
|
+
Used to specify primitive that should be redacted. If none of `secretKeys`, `deepSecretKeys`, or `fullSecretKeys` specified then all values are considered secret. Users should almost always specify at least one of these three fields. Has the lowest precedence of all configurable secret fields.
|
|
84
|
+
|
|
85
|
+
#### Details
|
|
86
|
+
- **Type:** `RegExp[]`
|
|
87
|
+
- **Default:** All values considered secret
|
|
88
|
+
- If `deepSecretKeys` or `fullSecretKeys` specified, defaults to `[]`
|
|
89
|
+
- **Effect:** Matches keys in objects or child objects and redacts their values, if primitive. Arrays are processed recursively, with primitives redacted.
|
|
90
|
+
- If the value is an object it will be assessed using the same logic, regardless of if its key value is a secret.
|
|
91
|
+
- if an array value is an object it will be assessed using the same logic, regardless of if its key value is a secret.
|
|
92
|
+
- Has lowest precedence when compared to `deepSecretKeys`, `fullSecretKeys`, and `customObjects` configurations.
|
|
93
|
+
|
|
94
|
+
#### Example
|
|
95
|
+
##### Code
|
|
96
|
+
```typescript
|
|
97
|
+
import { FieldRedactor } from 'field-redactor';
|
|
98
|
+
const myJsonObject = {
|
|
99
|
+
timestamp: "2024-12-01T22:07:26.448Z",
|
|
100
|
+
userId: 271,
|
|
101
|
+
contactInfo: {
|
|
102
|
+
email: "foo.bar@example.com",
|
|
103
|
+
firstName: "Foo",
|
|
104
|
+
lastName: "Bar",
|
|
105
|
+
Salutation: "Mr.",
|
|
106
|
+
preference: "email",
|
|
107
|
+
lastUpdated: "2024-12-01T22:07:26.448Z"
|
|
108
|
+
},
|
|
109
|
+
someSecretData: ["fizz", "buzz", { deep: "is not redacted", name: "foobar" }],
|
|
110
|
+
hobbies: ["Basketball", "Baseball", "Tennis"]
|
|
111
|
+
}
|
|
112
|
+
const fieldRedactor = new FieldRedactor({
|
|
113
|
+
secretKeys: [/email/i, /name/i, /someSecretData/i, /contactInfo/i],
|
|
114
|
+
});
|
|
115
|
+
const result = await fieldRedactor.redact(myJsonObject);
|
|
116
|
+
console.log(result);
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
##### Output
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"timestamp": "2024-12-01T22:07:26.448Z",
|
|
123
|
+
"userId": 271,
|
|
124
|
+
"contactInfo": {
|
|
125
|
+
"email": "REDACTED",
|
|
126
|
+
"firstName": "REDACTED",
|
|
127
|
+
"lastName": "REDACTED",
|
|
128
|
+
"Salutation": "Mr.",
|
|
129
|
+
"preference": "email",
|
|
130
|
+
"lastUpdated": "2024-12-01T22:07:26.448Z"
|
|
131
|
+
},
|
|
132
|
+
"someSecretData": ["REDACTED", "REDACTED", { "deep": "is not redacted", "name": "REDACTED" }],
|
|
133
|
+
"hobbies": ["Basketball", "Baseball", "Tennis"]
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
> - contactInfo was not redacted despite being a RegExp match as it is not a primitive.
|
|
138
|
+
> - `email`, `firstName`, and `lastName` fields were redacted in contactInfo as they matched RegExes
|
|
139
|
+
> - primitives in `someSecretData` array value were redacted
|
|
140
|
+
> - object in `someSecretData` was evaluated separate from primitives in the same array
|
|
141
|
+
|
|
142
|
+
### `deepSecretKeys` Configuration
|
|
143
|
+
Used to specify secrets which should be deeply redacted. Values which are objects or arrays will have all their values redacted, including child objects and arrays, unless identified as a `fullSecretKey` or `customObject` which take higher precedence.
|
|
144
|
+
|
|
145
|
+
#### Details
|
|
146
|
+
- **Type:** `RegExp[]`
|
|
147
|
+
- **Default:** `[]`
|
|
148
|
+
- **Effect:** Matches keys in objects or child objects and deeply redacts all values, including values in child objects or objects within arrays.
|
|
149
|
+
- Has higher precedence than `secretKeys` but lower precedence than `fullSecretKeys` and `customObjects`.
|
|
150
|
+
|
|
151
|
+
#### Example
|
|
152
|
+
##### Code
|
|
153
|
+
```typescript
|
|
154
|
+
import { FieldRedactor } from 'field-redactor';
|
|
155
|
+
const myJsonObject = {
|
|
156
|
+
timestamp: "2024-12-01T22:07:26.448Z",
|
|
157
|
+
userId: 271,
|
|
158
|
+
contactInfo: {
|
|
159
|
+
email: "foo.bar@example.com",
|
|
160
|
+
firstName: "Foo",
|
|
161
|
+
lastName: "Bar",
|
|
162
|
+
Salutation: "Mr.",
|
|
163
|
+
preference: "email",
|
|
164
|
+
lastUpdated: "2024-12-01T22:07:26.448Z",
|
|
165
|
+
lastUpdatedBy: {
|
|
166
|
+
firstName: "John",
|
|
167
|
+
lastName: "Doe",
|
|
168
|
+
id: "12345"
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
someSecretData: ["fizz", "buzz", { deep: "FOO", name: "BAR" }],
|
|
172
|
+
hobbies: ["Basketball", "Baseball", "Tennis", {contactInfo: "foobar"}]
|
|
173
|
+
}
|
|
174
|
+
const fieldRedactor = new FieldRedactor({
|
|
175
|
+
deepSecretKeys: [/someSecretData/i, /contactInfo/i]
|
|
176
|
+
});
|
|
177
|
+
const result = await fieldRedactor.redact(myJsonObject);
|
|
178
|
+
console.log(result);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
##### Output
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"timestamp": "2024-12-01T22:07:26.448Z",
|
|
185
|
+
"userId": 271,
|
|
186
|
+
"contactInfo": {
|
|
187
|
+
"email": "REDACTED",
|
|
188
|
+
"firstName": "REDACTED",
|
|
189
|
+
"lastName": "REDACTED",
|
|
190
|
+
"Salutation": "REDACTED",
|
|
191
|
+
"preference": "REDACTED",
|
|
192
|
+
"lastUpdated": "REDACTED",
|
|
193
|
+
"lastUpdatedBy": {
|
|
194
|
+
"firstName": "REDACTED",
|
|
195
|
+
"lastName": "REDACTED",
|
|
196
|
+
"id": "REDACTED"
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
"someSecretData": ["REDACTED", "REDACTED", { "deep": "REDACTED", "name": "REDACTED" }],
|
|
200
|
+
"hobbies": ["Basketball", "Baseball", "Tennis", { "contactInfo": "REDACTED" }]
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
> - All values in `contactInfo` were redacted, including deep children
|
|
204
|
+
> - All values in `someSecretData` were redacted, including object values
|
|
205
|
+
|
|
206
|
+
### `fullSecretKeys` Configuration
|
|
207
|
+
Specifies values which should be stringified and passed to the redactor function. Has higher precedence than other secret types but lower precedence than `customObjects`. However, if value is stringified and contains a child object which is a custom object then it will, by definition, not be noticed.
|
|
208
|
+
|
|
209
|
+
#### Details
|
|
210
|
+
- **Type:** `RegExp[]`
|
|
211
|
+
- **Default:** `[]`
|
|
212
|
+
- **Effect:** Matches keys in objects or child objects and stringifies and fully redacts their values.
|
|
213
|
+
- Has higher precedence than `secretKeys` and `deepSecretKeys` but lower precedence than `customObjects`.
|
|
214
|
+
|
|
215
|
+
#### Example
|
|
216
|
+
##### Code
|
|
217
|
+
```typescript
|
|
218
|
+
import { FieldRedactor } from 'field-redactor';
|
|
219
|
+
|
|
220
|
+
const myJsonObject = {
|
|
221
|
+
timestamp: "2024-12-01T22:07:26.448Z",
|
|
222
|
+
userId: 271,
|
|
223
|
+
contactInfo: {
|
|
224
|
+
email: "foo.bar@example.com",
|
|
225
|
+
firstName: "Foo",
|
|
226
|
+
lastName: "Bar",
|
|
227
|
+
Salutation: "Mr.",
|
|
228
|
+
preference: "email",
|
|
229
|
+
lastUpdated: "2024-12-01T22:07:26.448Z"
|
|
230
|
+
},
|
|
231
|
+
someSecretData: ["fizz", "buzz", { deep: "foo", name: "bar" }],
|
|
232
|
+
hobbies: ["Basketball", "Baseball", "Tennis"]
|
|
233
|
+
}
|
|
234
|
+
const fieldRedactor = new FieldRedactor({
|
|
235
|
+
fullSecretKeys: [/someSecretData/i, /contactInfo/i],
|
|
236
|
+
});
|
|
237
|
+
const result = await fieldRedactor.redact(myJsonObject);
|
|
238
|
+
console.log(result);
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
##### Output
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"timestamp": "2024-12-01T22:07:26.448Z",
|
|
245
|
+
"userId": 271,
|
|
246
|
+
"contactInfo": "REDACTED",
|
|
247
|
+
"someSecretData": "REDACTED",
|
|
248
|
+
"hobbies": ["Basketball", "Baseball", "Tennis"]
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
> - Entirity of `contactInfo` was redacted.
|
|
252
|
+
> - Entirity of `someSecretData` was redacted.
|
|
253
|
+
|
|
254
|
+
### `customObjects` Configuration
|
|
255
|
+
**One of the most powerful features of this library and why it was written in the first place.** Combining `secretKeys`, `deepSecretKeys`, `fullSecretKeys`, and `customObjects` yields a powerful and highly customizable redaction tool capable of conditionally redacting a broad variety of input JSON objects correctly according to a single schema configuration.
|
|
256
|
+
|
|
257
|
+
#### Details
|
|
258
|
+
- **Type:** `CustomObject` (See `CustomObject` Schema Section)
|
|
259
|
+
- **Default:** `[]`
|
|
260
|
+
- **Effect:** Any object that matches the schema will be considered a custom object and redacted based on the schema configuration.
|
|
261
|
+
- Priority supercedes all others. If a `CustomObject` is nested inside a `deepSecretKey` value then the `CustomObject` configuration takes precedence.
|
|
262
|
+
- If a `CustomObject` is found inside another `CustomObject` then the new `CustomObject` takes precedence.
|
|
263
|
+
|
|
264
|
+
#### `CustomObject` Schema
|
|
265
|
+
A custom object takes the following format:
|
|
266
|
+
```typescript
|
|
267
|
+
{
|
|
268
|
+
[key]: CustomObjectMatchType | string
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
- `key`
|
|
273
|
+
- **Type:** `string`
|
|
274
|
+
- **Effect:** Specifies the key for a custom object
|
|
275
|
+
- `value`:
|
|
276
|
+
- **Type:** `CustomObjectMatchType | string`
|
|
277
|
+
- **Effect:** Specifies how the value should be redacted
|
|
278
|
+
- `string` - checks if the sibling key with this name has a value which is a `secretKey`, `deepSecretKey`, or `fullSecretKey` and redacts accordingly
|
|
279
|
+
- `CustomObjectMatchType` - Redacts according to the match type. See `CustomObjectMatchType` Enum Section.
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
#### `CustomObjectMatchType` Enum
|
|
283
|
+
| Key | Description |
|
|
284
|
+
| --- | ----------- |
|
|
285
|
+
| `Full` | Stringify value and redact. Functions identical to `fullSecretKeys`. |
|
|
286
|
+
| `Deep` | Redact if primitive, deeply redact all values and children if object or array. Functions identical to `deepSecretKeys`. |
|
|
287
|
+
| `Shallow` | Redact if primitive or array of primitives and revery to normal evaluation if object or array contains object. Functions identical to `secretKeys`. |
|
|
288
|
+
| `Pass` | Do not redact if primitive value and revert to normal rules if an object. |
|
|
289
|
+
| `Ignore` | Do not redact if primitive and skip evaluation entirely if object or array. |
|
|
290
|
+
|
|
291
|
+
#### Example
|
|
292
|
+
```typescript
|
|
293
|
+
import { FieldRedactor } from 'field-redactor';
|
|
294
|
+
|
|
295
|
+
const myCustomObject = {
|
|
296
|
+
name: CustomObjectMatchType.Ignore,
|
|
297
|
+
type: CustomObjectMatchType.Ignore,
|
|
298
|
+
significance: CustomObjectMatchType.Ignore,
|
|
299
|
+
shallowValue: "name",
|
|
300
|
+
deepValue: "type",
|
|
301
|
+
fullValue: "significance"
|
|
302
|
+
shallow: CustomObjectMatchType.Shallow,
|
|
303
|
+
deep: CustomObjectMatchType.Deep,
|
|
304
|
+
full: CustomObjectMatchType.Full,
|
|
305
|
+
pass: CustomObjectMatchType.Pass,
|
|
306
|
+
ignore: CustomObjectMatchType.Ignore
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const myJsonObject = {
|
|
310
|
+
timestamp: "2024-12-01T22:07:26.448Z",
|
|
311
|
+
userId: 271,
|
|
312
|
+
data: [
|
|
313
|
+
{
|
|
314
|
+
name: "email",
|
|
315
|
+
type: "Secure",
|
|
316
|
+
significance: "meta"
|
|
317
|
+
shallowValue: "foo.bar@example.com",
|
|
318
|
+
deepValue: "foobar",
|
|
319
|
+
fullValue: "hello",
|
|
320
|
+
shallow: "hello",
|
|
321
|
+
deep: "hello",
|
|
322
|
+
full: "hello",
|
|
323
|
+
pass: "hello",
|
|
324
|
+
ignore: "hello"
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: "email",
|
|
328
|
+
type: "Secure",
|
|
329
|
+
significance: "meta",
|
|
330
|
+
shallowValue: {
|
|
331
|
+
hello: "world",
|
|
332
|
+
email: "foobar"
|
|
333
|
+
},
|
|
334
|
+
deepValue: {
|
|
335
|
+
hello: "world",
|
|
336
|
+
email: "foobar"
|
|
337
|
+
},
|
|
338
|
+
deepValue: {
|
|
339
|
+
hello: "world",
|
|
340
|
+
email: "foobar"
|
|
341
|
+
},
|
|
342
|
+
shallow: {
|
|
343
|
+
hello: "world",
|
|
344
|
+
email: "foobar"
|
|
345
|
+
},
|
|
346
|
+
deep: {
|
|
347
|
+
hello: "world",
|
|
348
|
+
email: "foobar"
|
|
349
|
+
},
|
|
350
|
+
full: {
|
|
351
|
+
hello: "world",
|
|
352
|
+
email: "foobar"
|
|
353
|
+
},
|
|
354
|
+
pass: {
|
|
355
|
+
hello: "world",
|
|
356
|
+
email: "foobar"
|
|
357
|
+
},
|
|
358
|
+
ignore: {
|
|
359
|
+
hello: "world",
|
|
360
|
+
email: "foobar"
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
]
|
|
364
|
+
};
|
|
365
|
+
const fieldRedactor = new FieldRedactor({
|
|
366
|
+
secretKeys: [/email/],
|
|
367
|
+
deepSecretKeys: [/secure/i],
|
|
368
|
+
fullSecretKeys: [/meta/i],
|
|
369
|
+
customObjects: [myCustomObject]
|
|
370
|
+
});
|
|
371
|
+
const result = await fieldRedactor.redact(myJsonObject);
|
|
372
|
+
console.log(result);
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
##### Output
|
|
376
|
+
```json
|
|
377
|
+
{
|
|
378
|
+
"timestamp": "2024-12-01T22:07:26.448Z",
|
|
379
|
+
"userId": 271,
|
|
380
|
+
"data": [
|
|
381
|
+
{
|
|
382
|
+
"name": "email",
|
|
383
|
+
"type": "Secure",
|
|
384
|
+
"shallowValue": "REDACTED",
|
|
385
|
+
"deepValue": "REDACTED",
|
|
386
|
+
"fullValue": "REDACTED",
|
|
387
|
+
"shallow": "REDACTED",
|
|
388
|
+
"deep": "REDACTED",
|
|
389
|
+
"full": "REDACTED",
|
|
390
|
+
"pass": "hello",
|
|
391
|
+
"ignore": "hello"
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"name": "email",
|
|
395
|
+
"type": "Secure",
|
|
396
|
+
"shallowValue": {
|
|
397
|
+
"hello": "world",
|
|
398
|
+
"email": "REDACTED"
|
|
399
|
+
},
|
|
400
|
+
"deepValue": {
|
|
401
|
+
"hello": "REDACTED",
|
|
402
|
+
"email": "REDACTED"
|
|
403
|
+
},
|
|
404
|
+
"fullValue": "REDACTED",
|
|
405
|
+
"shallow": {
|
|
406
|
+
"hello": "world",
|
|
407
|
+
"email": "REDACTED"
|
|
408
|
+
},
|
|
409
|
+
"deep": {
|
|
410
|
+
"hello": "REDACTED",
|
|
411
|
+
"email": "REDACTED"
|
|
412
|
+
},
|
|
413
|
+
"full": "REDACTED",
|
|
414
|
+
"pass": {
|
|
415
|
+
"hello": "world",
|
|
416
|
+
"email": "REDACTED"
|
|
417
|
+
},
|
|
418
|
+
"ignore": {
|
|
419
|
+
"hello": "world",
|
|
420
|
+
"email": "foobar"
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
]
|
|
424
|
+
};
|
|
425
|
+
```
|
|
426
|
+
> - Both objects in the data array matched the custom object and were redacted accordingly
|
|
427
|
+
> - The first object contains only primitives whereas the second contains objects to highlight the differences
|
|
428
|
+
> - `name` and `type` field were never redacted in either object because `CustomObjectMatchType` was `Ignore`
|
|
429
|
+
> - `shallowValue`, `deepValue`, and `fullValue` had string key specifiers which matched a `shallowKey`, `deepSecretKey`, and `fullSecretKey`, respectively, and were redacted according to those rules.
|
|
430
|
+
> - `shallow`, `deep`, `full`, `pass`, and `ignore` fields were redacted according to their `CustomObjectMatchType` value specified in the schema.
|
|
431
|
+
|
|
432
|
+
### `ignoreBooleans` Configuration
|
|
433
|
+
- **Type:** `boolean`
|
|
434
|
+
- **Default:** `false`
|
|
435
|
+
- **Effect:** Specifies if boolean values should be redacted.
|
|
436
|
+
|
|
437
|
+
### Example
|
|
438
|
+
#### Code
|
|
439
|
+
```typescript
|
|
440
|
+
import { FieldRedactor } from 'field-redactor';
|
|
441
|
+
|
|
442
|
+
const myJsonObject = {
|
|
443
|
+
foo: "bar",
|
|
444
|
+
fizz: false,
|
|
445
|
+
buzz: true
|
|
446
|
+
};
|
|
447
|
+
const fieldRedactor = new FieldRedactor({
|
|
448
|
+
ignoreBooleans: true,
|
|
449
|
+
secretKeys: [/foo/, /fizz/, /buzz/]
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const result = await fieldRedactor.redact(myJsonObject);
|
|
453
|
+
console.log(result);
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
#### Output
|
|
457
|
+
```json
|
|
458
|
+
{
|
|
459
|
+
"foo": "REDACTED",
|
|
460
|
+
"fizz": false,
|
|
461
|
+
"buzz": true
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### `ignoreNullOrUndefined` Configuration
|
|
466
|
+
- **Type:** `boolean`
|
|
467
|
+
- **Default:** `true`
|
|
468
|
+
- **Effect:** Specifies if null or undefined values shold be redacted.
|
|
469
|
+
- *Note: Ensure custom redaction function can appropriately handle null or undefined if set to false.*
|
|
470
|
+
|
|
471
|
+
### Example
|
|
472
|
+
#### Code
|
|
473
|
+
```typescript
|
|
474
|
+
const myJsonObject = {
|
|
475
|
+
foo: "bar",
|
|
476
|
+
fizz: null,
|
|
477
|
+
buzz: undefined
|
|
478
|
+
};
|
|
479
|
+
const fieldRedactor = new FieldRedactor({
|
|
480
|
+
ignoreNullOrUndefined: false
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const result = await fieldRedactor.redact(myJsonObject);
|
|
484
|
+
console.log(result);
|
|
485
|
+
```
|
|
486
|
+
#### Output
|
|
487
|
+
```json
|
|
488
|
+
{
|
|
489
|
+
"foo": "REDACTED",
|
|
490
|
+
"fizz": "REDACTED",
|
|
491
|
+
"buzz": "REDACTED"
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
## Full Example
|
|
496
|
+
The following example illustrates the power and utility of this library when conditionally redacting JSON output for logging or other purposes. It allows users to specify the manner of redaction, which fields should be redacted and how, and specify custom object schemas with highly configurable redaction logic based on sibling keys or set rules. I find it a quite useful tool!
|
|
497
|
+
|
|
498
|
+
##### Code
|
|
499
|
+
```typescript
|
|
500
|
+
import { FieldRedactor } from 'field-redactor';
|
|
501
|
+
const myRedactor = (text: string) => Promise.resolve("REDACTED");
|
|
502
|
+
const metadataCustomObject: CustomObject = {
|
|
503
|
+
name: CustomObjectMatchTypes.Pass,
|
|
504
|
+
type: CustomObjectMatchTypes.Pass,
|
|
505
|
+
id: CustomObjectMatchTypes.Shallow,
|
|
506
|
+
value: "name"
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
const actionsCustomObject: CustomObject = {
|
|
510
|
+
userId: CustomObjectMatchTypes.Pass,
|
|
511
|
+
field: CustomObjectMatchTypes.Pass,
|
|
512
|
+
action: CustomObjectMatchTypes.Pass,
|
|
513
|
+
value: "field"
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const fieldRedactor: FieldRedactor = new FieldRedactor({
|
|
517
|
+
redactor: myRedactor,
|
|
518
|
+
secretKeys: [/email/, /name/i, /someSecretData/, /children/],
|
|
519
|
+
deepSecretKeys: [/accountInfo/i, /someDeepSecretData/i, /privateInfo/i],
|
|
520
|
+
customObjects: [metadataCustomObject, actionsCustomObject],
|
|
521
|
+
ignoreNullOrUndefined: false
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
const myJsonObjectToRedact = {
|
|
525
|
+
timestamp: "2024-12-01T22:07:26.448Z",
|
|
526
|
+
userId: 271,
|
|
527
|
+
contactInfo: {
|
|
528
|
+
email: "foo.bar@example.com",
|
|
529
|
+
salutation: "Mr.",
|
|
530
|
+
firstName: "Foo",
|
|
531
|
+
lastName: "Bar",
|
|
532
|
+
dob: "1980-01-01",
|
|
533
|
+
backupEmail: undefined,
|
|
534
|
+
preference: "email",
|
|
535
|
+
lastUpdated: "2024-12-01T22:07:26.448Z"
|
|
536
|
+
},
|
|
537
|
+
someSecretData: ["fizz", "buzz", { deep: "is not redacted", name: "foobar" }],
|
|
538
|
+
accountInfo: {
|
|
539
|
+
balance: 123.45,
|
|
540
|
+
institution: "FizzBuz International"
|
|
541
|
+
information: {
|
|
542
|
+
routingNumber: 11111111,
|
|
543
|
+
acctNumber: 222222
|
|
544
|
+
}
|
|
545
|
+
},
|
|
546
|
+
actions: [
|
|
547
|
+
{
|
|
548
|
+
userId: 271,
|
|
549
|
+
field: "email",
|
|
550
|
+
action: "CREATE",
|
|
551
|
+
value: "foo.bar@example.com"
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
userId: 271,
|
|
555
|
+
field: "preference",
|
|
556
|
+
action: "UPDATE",
|
|
557
|
+
value: "email"
|
|
558
|
+
}
|
|
559
|
+
],
|
|
560
|
+
metadata: [
|
|
561
|
+
{
|
|
562
|
+
name: "mdn",
|
|
563
|
+
type: "Number",
|
|
564
|
+
id: 12,
|
|
565
|
+
value: 16151112222
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
name: "children",
|
|
569
|
+
type: "Array",
|
|
570
|
+
id: 20,
|
|
571
|
+
value: ["John", "Paul","Ringo", "George"]
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
name: "traceId",
|
|
575
|
+
type: "String",
|
|
576
|
+
id: 10,
|
|
577
|
+
value: "1234-6587"
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
name: "privateInfo",
|
|
581
|
+
type: "Object",
|
|
582
|
+
id: 20,
|
|
583
|
+
value: {
|
|
584
|
+
mySecretThings: {
|
|
585
|
+
a: "foo",
|
|
586
|
+
b: "bar"
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
],
|
|
591
|
+
hobbies: ["Basketball", "Baseball", "Tennis"],
|
|
592
|
+
someDeepSecretData: ["fizz", "buzz", { deep: "is redacted", name: "foobar" }],
|
|
593
|
+
someFullSecretData: {
|
|
594
|
+
foo: "bar"
|
|
595
|
+
},
|
|
596
|
+
someFullSecretData2: ["a", 1, "12"]
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const result = await fieldRedactor.redact(myJsonObject);
|
|
600
|
+
console.log(result);
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
##### Output
|
|
604
|
+
```json
|
|
605
|
+
{
|
|
606
|
+
"timestamp": "2024-12-01T22:07:26.448Z",
|
|
607
|
+
"userId": 271,
|
|
608
|
+
"contactInfo": {
|
|
609
|
+
"email": "REDACTED",
|
|
610
|
+
"salutation": "Mr.",
|
|
611
|
+
"firstName": "REDACTED",
|
|
612
|
+
"lastName": "REDACTED",
|
|
613
|
+
"dob": "REDACTED",
|
|
614
|
+
"backupEmail": "REDACTED",
|
|
615
|
+
"preference": "email",
|
|
616
|
+
"lastUpdated": "2024-12-01T22:07:26.448Z",
|
|
617
|
+
},
|
|
618
|
+
"someSecretData": ["REDACTED", "REDACTED", { "deep": "is not redacted", "name": "REDACTED" }],
|
|
619
|
+
"accountInfo": {
|
|
620
|
+
"balance": "REDACTED",
|
|
621
|
+
"institution": "REDACTED",
|
|
622
|
+
"information": {
|
|
623
|
+
"routingNumber": "REDACTED",
|
|
624
|
+
"acctNumber": "REDACTED"
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
"actions": [
|
|
628
|
+
{
|
|
629
|
+
"userId": 271,
|
|
630
|
+
"field": "email",
|
|
631
|
+
"action": "CREATE",
|
|
632
|
+
"value": "REDACTED"
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
"userId": 271,
|
|
636
|
+
"field": "preference",
|
|
637
|
+
"action": "UPDATE",
|
|
638
|
+
"value": "email"
|
|
639
|
+
}
|
|
640
|
+
],
|
|
641
|
+
"metadata": [
|
|
642
|
+
{
|
|
643
|
+
"name": "mdn",
|
|
644
|
+
"type": "Number",
|
|
645
|
+
"id": "REDACTED",
|
|
646
|
+
"value": "REDACTED"
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
"name": "children",
|
|
650
|
+
"type": "Array",
|
|
651
|
+
"id": "REDACTED",
|
|
652
|
+
"value": ["REDACTED", "REDACTED", "REDACTED", "REDACTED"]
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
"name": "traceId",
|
|
656
|
+
"type": "String",
|
|
657
|
+
"id": 10,
|
|
658
|
+
"value": "1234-6587"
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
"name": "privateInfo",
|
|
662
|
+
"type": "Object",
|
|
663
|
+
"id": "REDACTED",
|
|
664
|
+
"value": {
|
|
665
|
+
"mySecretThings": {
|
|
666
|
+
"a": "REDACTED",
|
|
667
|
+
"b": "REDACTED"
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
],
|
|
672
|
+
"hobbies": ["Basketball", "Baseball", "Tennis"],
|
|
673
|
+
"someDeepSecretData": ["REDACTED", "REDACTED", { "deep": "REDACTED", "name": "REDACTED" }],
|
|
674
|
+
"someFullSecretData": "REDACTED",
|
|
675
|
+
"someFullSecretData2": "REDACTED"
|
|
676
|
+
}
|
|
677
|
+
```
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CustomObject } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Utility for determining if a given object matches a CustomObject schema.
|
|
4
|
+
*/
|
|
5
|
+
export declare class CustomObjectChecker {
|
|
6
|
+
private customObjects;
|
|
7
|
+
/**
|
|
8
|
+
* Creates a CustomObjectChecker with the specified CustomObjects.
|
|
9
|
+
* @param customObjects The CustomObjects to check against.
|
|
10
|
+
*/
|
|
11
|
+
constructor(customObjects?: CustomObject[]);
|
|
12
|
+
/**
|
|
13
|
+
* Determines if the input value matches any of the custom objects provided in the constructor.
|
|
14
|
+
* @param value The value to compare against the custom objects.
|
|
15
|
+
* @returns A matching custom object if one exists, otherwise undefined.
|
|
16
|
+
*/
|
|
17
|
+
getMatchingCustomObject(value: any): CustomObject | undefined;
|
|
18
|
+
private isCustomObject;
|
|
19
|
+
private customObjectDoesNotHaveExtraKeys;
|
|
20
|
+
private validateCustomObjects;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=customObjectChecker.d.ts.map
|