field-redactor 1.2.0 → 1.5.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/.github/workflows/ci.yml +38 -0
- package/CHANGELOG.md +156 -0
- package/README.md +64 -670
- package/dist/configValidator.d.ts +10 -0
- package/dist/configValidator.d.ts.map +1 -0
- package/dist/configValidator.js +94 -0
- package/dist/configValidator.js.map +1 -0
- package/dist/copyOnWriteHelpers.d.ts +20 -0
- package/dist/copyOnWriteHelpers.d.ts.map +1 -0
- package/dist/copyOnWriteHelpers.js +58 -0
- package/dist/copyOnWriteHelpers.js.map +1 -0
- package/dist/customObjectManager.d.ts +13 -7
- package/dist/customObjectManager.d.ts.map +1 -1
- package/dist/customObjectManager.js +30 -29
- package/dist/customObjectManager.js.map +1 -1
- package/dist/dryRun.d.ts +8 -0
- package/dist/dryRun.d.ts.map +1 -0
- package/dist/dryRun.js +75 -0
- package/dist/dryRun.js.map +1 -0
- package/dist/dryRunAttribution.d.ts +8 -0
- package/dist/dryRunAttribution.d.ts.map +1 -0
- package/dist/dryRunAttribution.js +106 -0
- package/dist/dryRunAttribution.js.map +1 -0
- package/dist/fieldRedactor.d.ts +43 -5
- package/dist/fieldRedactor.d.ts.map +1 -1
- package/dist/fieldRedactor.js +124 -24
- package/dist/fieldRedactor.js.map +1 -1
- package/dist/fieldRedactorConfigBuilder.d.ts +49 -0
- package/dist/fieldRedactorConfigBuilder.d.ts.map +1 -0
- package/dist/fieldRedactorConfigBuilder.js +98 -0
- package/dist/fieldRedactorConfigBuilder.js.map +1 -0
- package/dist/fieldRedactorDeps.d.ts +17 -0
- package/dist/fieldRedactorDeps.d.ts.map +1 -0
- package/dist/fieldRedactorDeps.js +41 -0
- package/dist/fieldRedactorDeps.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/jsonWalk.d.ts +11 -0
- package/dist/jsonWalk.d.ts.map +1 -0
- package/dist/jsonWalk.js +86 -0
- package/dist/jsonWalk.js.map +1 -0
- package/dist/objectRedactor.d.ts +9 -34
- package/dist/objectRedactor.d.ts.map +1 -1
- package/dist/objectRedactor.js +11 -298
- package/dist/objectRedactor.js.map +1 -1
- package/dist/objectRedactorCow.d.ts +35 -0
- package/dist/objectRedactorCow.d.ts.map +1 -0
- package/dist/objectRedactorCow.js +300 -0
- package/dist/objectRedactorCow.js.map +1 -0
- package/dist/objectRedactorCustomObject.d.ts +49 -0
- package/dist/objectRedactorCustomObject.d.ts.map +1 -0
- package/dist/objectRedactorCustomObject.js +138 -0
- package/dist/objectRedactorCustomObject.js.map +1 -0
- package/dist/objectRedactorHelpers.d.ts +9 -0
- package/dist/objectRedactorHelpers.d.ts.map +1 -0
- package/dist/objectRedactorHelpers.js +58 -0
- package/dist/objectRedactorHelpers.js.map +1 -0
- package/dist/objectRedactorMutation.d.ts +10 -0
- package/dist/objectRedactorMutation.d.ts.map +1 -0
- package/dist/objectRedactorMutation.js +89 -0
- package/dist/objectRedactorMutation.js.map +1 -0
- package/dist/objectRedactorSync.d.ts +2 -0
- package/dist/objectRedactorSync.d.ts.map +1 -0
- package/dist/objectRedactorSync.js +7 -0
- package/dist/objectRedactorSync.js.map +1 -0
- package/dist/objectRedactorTraversal.d.ts +57 -0
- package/dist/objectRedactorTraversal.d.ts.map +1 -0
- package/dist/objectRedactorTraversal.js +576 -0
- package/dist/objectRedactorTraversal.js.map +1 -0
- package/dist/presets.d.ts +22 -0
- package/dist/presets.d.ts.map +1 -0
- package/dist/presets.js +50 -0
- package/dist/presets.js.map +1 -0
- package/dist/primitiveRedactor.d.ts +13 -8
- package/dist/primitiveRedactor.d.ts.map +1 -1
- package/dist/primitiveRedactor.js +32 -27
- package/dist/primitiveRedactor.js.map +1 -1
- package/dist/redactionRules.d.ts +23 -0
- package/dist/redactionRules.d.ts.map +1 -0
- package/dist/redactionRules.js +76 -0
- package/dist/redactionRules.js.map +1 -0
- package/dist/regexUtils.d.ts +3 -0
- package/dist/regexUtils.d.ts.map +1 -0
- package/dist/regexUtils.js +8 -0
- package/dist/regexUtils.js.map +1 -0
- package/dist/secretManager.d.ts +12 -6
- package/dist/secretManager.d.ts.map +1 -1
- package/dist/secretManager.js +40 -14
- package/dist/secretManager.js.map +1 -1
- package/dist/types.d.ts +103 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +7 -1
- package/dist/types.js.map +1 -1
- package/dist/valuePatternMatcher.d.ts +15 -0
- package/dist/valuePatternMatcher.d.ts.map +1 -0
- package/dist/valuePatternMatcher.js +30 -0
- package/dist/valuePatternMatcher.js.map +1 -0
- package/docs/guides/anti-patterns.md +107 -0
- package/docs/guides/metadata-redaction.md +112 -0
- package/docs/guides/migration-1.2-to-1.5.md +106 -0
- package/docs/guides/secret-key-modes.md +108 -0
- package/docs/guides/value-pattern-redaction.md +58 -0
- package/docs/reference/config.md +135 -0
- package/docs/release-notes/2.0.0.md +19 -0
- package/docs/release-notes/2.1.0.md +17 -0
- package/docs/release-notes/2.2.0.md +16 -0
- package/docs/release-notes/2.3.0.md +17 -0
- package/docs/release-notes/2.3.1.md +17 -0
- package/docs/release-notes/2.4.0.md +30 -0
- package/docs/release-notes/2.5.0.md +33 -0
- package/docs/release-notes/2.5.1.md +17 -0
- package/docs/release-notes/README.md +30 -0
- package/docs/release-notes/v1.0.0.md +17 -0
- package/docs/release-notes/v1.1.0.md +18 -0
- package/docs/release-notes/v1.2.0.md +18 -0
- package/docs/release-notes/v1.2.1.md +16 -0
- package/docs/release-notes/v1.2.2.md +19 -0
- package/docs/release-notes/v1.3.0.md +112 -0
- package/docs/release-notes/v1.5.0.md +88 -0
- package/jest.config.js +4 -1
- package/package.json +3 -3
package/dist/types.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CustomObjectMatchType = void 0;
|
|
3
|
+
exports.isJsonObject = exports.CustomObjectMatchType = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Per-field redaction mode inside a {@link CustomObject} schema.
|
|
6
|
+
* Values align with the top-level doc labels: Shallow, Deep, Opaque (Full), Remove (Delete).
|
|
7
|
+
*/
|
|
4
8
|
var CustomObjectMatchType;
|
|
5
9
|
(function (CustomObjectMatchType) {
|
|
6
10
|
CustomObjectMatchType[CustomObjectMatchType["Delete"] = 0] = "Delete";
|
|
@@ -10,4 +14,6 @@ var CustomObjectMatchType;
|
|
|
10
14
|
CustomObjectMatchType[CustomObjectMatchType["Pass"] = 4] = "Pass";
|
|
11
15
|
CustomObjectMatchType[CustomObjectMatchType["Ignore"] = 5] = "Ignore";
|
|
12
16
|
})(CustomObjectMatchType || (exports.CustomObjectMatchType = CustomObjectMatchType = {}));
|
|
17
|
+
const isJsonObject = (value) => !!value && typeof value === 'object' && !(value instanceof Date) && !Array.isArray(value);
|
|
18
|
+
exports.isJsonObject = isJsonObject;
|
|
13
19
|
//# sourceMappingURL=types.js.map
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AA4CA;;;GAGG;AACH,IAAY,qBAOX;AAPD,WAAY,qBAAqB;IAC/B,qEAAM,CAAA;IACN,iEAAI,CAAA;IACJ,iEAAI,CAAA;IACJ,uEAAO,CAAA;IACP,iEAAI,CAAA;IACJ,qEAAM,CAAA;AACR,CAAC,EAPW,qBAAqB,qCAArB,qBAAqB,QAOhC;AAkGM,MAAM,YAAY,GAAG,CAAC,KAA4B,EAAuB,EAAE,CAChF,CAAC,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,YAAY,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAD/E,QAAA,YAAY,gBACmE"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RedactablePrimitive } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Opt-in regex matching against scalar **values** (not key names).
|
|
4
|
+
* Applied only when key-based rules do not already redact the field.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ValuePatternMatcher {
|
|
7
|
+
private readonly patterns;
|
|
8
|
+
constructor(patterns?: RegExp[]);
|
|
9
|
+
hasPatterns(): boolean;
|
|
10
|
+
findMatching(value: RedactablePrimitive | undefined): RegExp | undefined;
|
|
11
|
+
formatPattern(regex: RegExp): string;
|
|
12
|
+
}
|
|
13
|
+
/** Shared empty matcher for tests and configs without value patterns. */
|
|
14
|
+
export declare const EMPTY_VALUE_PATTERN_MATCHER: ValuePatternMatcher;
|
|
15
|
+
//# sourceMappingURL=valuePatternMatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"valuePatternMatcher.d.ts","sourceRoot":"","sources":["../src/valuePatternMatcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAE9C;;;GAGG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;gBAExB,QAAQ,CAAC,EAAE,MAAM,EAAE;IAIxB,WAAW,IAAI,OAAO;IAItB,YAAY,CAAC,KAAK,EAAE,mBAAmB,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS;IASxE,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAG5C;AAED,yEAAyE;AACzE,eAAO,MAAM,2BAA2B,qBAA4B,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EMPTY_VALUE_PATTERN_MATCHER = exports.ValuePatternMatcher = void 0;
|
|
4
|
+
const regexUtils_1 = require("./regexUtils");
|
|
5
|
+
/**
|
|
6
|
+
* Opt-in regex matching against scalar **values** (not key names).
|
|
7
|
+
* Applied only when key-based rules do not already redact the field.
|
|
8
|
+
*/
|
|
9
|
+
class ValuePatternMatcher {
|
|
10
|
+
constructor(patterns) {
|
|
11
|
+
this.patterns = patterns !== null && patterns !== void 0 ? patterns : [];
|
|
12
|
+
}
|
|
13
|
+
hasPatterns() {
|
|
14
|
+
return this.patterns.length > 0;
|
|
15
|
+
}
|
|
16
|
+
findMatching(value) {
|
|
17
|
+
if (value === undefined || value === null || typeof value === 'boolean') {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const text = String(value);
|
|
21
|
+
return this.patterns.find((pattern) => pattern.test(text));
|
|
22
|
+
}
|
|
23
|
+
formatPattern(regex) {
|
|
24
|
+
return (0, regexUtils_1.formatRegExp)(regex);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.ValuePatternMatcher = ValuePatternMatcher;
|
|
28
|
+
/** Shared empty matcher for tests and configs without value patterns. */
|
|
29
|
+
exports.EMPTY_VALUE_PATTERN_MATCHER = new ValuePatternMatcher();
|
|
30
|
+
//# sourceMappingURL=valuePatternMatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"valuePatternMatcher.js","sourceRoot":"","sources":["../src/valuePatternMatcher.ts"],"names":[],"mappings":";;;AAAA,6CAA4C;AAG5C;;;GAGG;AACH,MAAa,mBAAmB;IAG9B,YAAY,QAAmB;QAC7B,IAAI,CAAC,QAAQ,GAAG,QAAQ,aAAR,QAAQ,cAAR,QAAQ,GAAI,EAAE,CAAC;IACjC,CAAC;IAEM,WAAW;QAChB,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAClC,CAAC;IAEM,YAAY,CAAC,KAAsC;QACxD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;YACxE,OAAO,SAAS,CAAC;QACnB,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3B,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7D,CAAC;IAEM,aAAa,CAAC,KAAa;QAChC,OAAO,IAAA,yBAAY,EAAC,KAAK,CAAC,CAAC;IAC7B,CAAC;CACF;AAvBD,kDAuBC;AAED,yEAAyE;AAC5D,QAAA,2BAA2B,GAAG,IAAI,mBAAmB,EAAE,CAAC"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Configuration anti-patterns
|
|
2
|
+
|
|
3
|
+
Common mistakes when configuring Field Redactor — and what to do instead. Many of these are also surfaced as `configWarnings` at construction time; set `strict: true` to fail fast in tests.
|
|
4
|
+
|
|
5
|
+
## Using `new FieldRedactor()` without rules
|
|
6
|
+
|
|
7
|
+
**Problem:** With no `secretKeys`, `deepSecretKeys`, `fullSecretKeys`, `deleteSecretKeys`, or `customObjects`, every value is redacted. Easy to ship a config that wipes entire payloads.
|
|
8
|
+
|
|
9
|
+
**Instead:** Use `FieldRedactor.createSafe()`, `FieldRedactorConfigBuilder.buildSafeRedactor()`, or the builder with at least one explicit rule.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// Avoid
|
|
13
|
+
const redactor = new FieldRedactor();
|
|
14
|
+
|
|
15
|
+
// Prefer
|
|
16
|
+
const redactor = FieldRedactor.createSafe({ secretKeys: [/email/i] });
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Same regex in multiple key groups
|
|
20
|
+
|
|
21
|
+
**Problem:** `/email/i` in both `secretKeys` and `deepSecretKeys` is confusing — only the higher-precedence group applies (Opaque → Deep → Remove → Shallow).
|
|
22
|
+
|
|
23
|
+
**Instead:** Pick one mode per pattern. Use `dryRun()` and inspect `report.pathRules` to confirm which rule fired.
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
const { report } = redactor.dryRunSync(sample);
|
|
27
|
+
// report.pathRules → [{ path: 'email', action: 'redact', rule: 'shallow', pattern: '/email/i' }]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Global (`g`) regex flags
|
|
31
|
+
|
|
32
|
+
**Problem:** Global regexes can leave `lastIndex` set and make repeated `.test()` calls unreliable.
|
|
33
|
+
|
|
34
|
+
**Instead:** Remove the `g` flag from config patterns.
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// Avoid
|
|
38
|
+
secretKeys: [/email/gi]
|
|
39
|
+
|
|
40
|
+
// Prefer
|
|
41
|
+
secretKeys: [/email/i]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Overlapping schemas
|
|
45
|
+
|
|
46
|
+
**Problem:** Two `customObjects` where one schema’s keys are a subset of another’s can both match the same object. Only the schema with **more keys** wins.
|
|
47
|
+
|
|
48
|
+
**Instead:** Make schemas disjoint, or order specificity by adding distinguishing keys to the more specific schema.
|
|
49
|
+
|
|
50
|
+
## Identical schema key sets
|
|
51
|
+
|
|
52
|
+
**Problem:** Two schemas with the exact same keys throw `FieldRedactorConfigurationError` at construction.
|
|
53
|
+
|
|
54
|
+
**Instead:** Merge into one schema or differentiate the key sets.
|
|
55
|
+
|
|
56
|
+
## Schema sibling reference typos
|
|
57
|
+
|
|
58
|
+
**Problem:** `value: 'name'` in a schema requires a `name` key on the schema object. A typo references a sibling that is not part of the schema definition.
|
|
59
|
+
|
|
60
|
+
**Instead:** Ensure every string sibling reference appears as a key on the schema. The validator warns when it does not.
|
|
61
|
+
|
|
62
|
+
## Relying on schemas when keys are stable
|
|
63
|
+
|
|
64
|
+
**Problem:** `customObjects` add complexity. If field names alone identify sensitive data (`password`, `ssn`), schemas are unnecessary.
|
|
65
|
+
|
|
66
|
+
**Instead:** Use Shallow / Deep / Opaque / Remove on key names. Reserve schemas for sibling-key patterns like `{ name, value }`.
|
|
67
|
+
|
|
68
|
+
## Expecting value-based detection
|
|
69
|
+
|
|
70
|
+
**Problem:** `secretKeys: [/email/]` matches **key names**, not values. A field named `description` containing `alice@example.com` is not redacted by key rules alone.
|
|
71
|
+
|
|
72
|
+
**Instead:** Opt in to **value-pattern** redaction with `valuePatterns`, add a schema when sensitivity depends on a sibling, or add explicit keys.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
FieldRedactor.createSafe({
|
|
76
|
+
valuePatterns: [/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/]
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
See [Value-pattern redaction](value-pattern-redaction.md).
|
|
81
|
+
|
|
82
|
+
## Ignoring `dryRun` before production
|
|
83
|
+
|
|
84
|
+
**Problem:** Guessing which paths change on real log shapes.
|
|
85
|
+
|
|
86
|
+
**Instead:** Run `dryRunSync()` on representative payloads and review `redactedPaths`, `deletedPaths`, `matchedSchemas`, and `pathRules`.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
FieldRedactorConfigBuilder.create()
|
|
90
|
+
.usePreset(presets.applicationLogging())
|
|
91
|
+
.shallow(/ssn/i)
|
|
92
|
+
.buildSafeRedactor()
|
|
93
|
+
.dryRunSync(productionSample);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Mutating shared references unintentionally
|
|
97
|
+
|
|
98
|
+
**Problem:** With `cloneInput: false`, `redact()` mutates the input. Shared object references elsewhere in the process may change.
|
|
99
|
+
|
|
100
|
+
**Instead:** Keep the default `cloneInput: true` (copy-on-write) unless you intentionally own the input object.
|
|
101
|
+
|
|
102
|
+
## Related guides
|
|
103
|
+
|
|
104
|
+
- [Secret key modes](secret-key-modes.md)
|
|
105
|
+
- [Metadata redaction](metadata-redaction.md)
|
|
106
|
+
- [Value-pattern redaction](value-pattern-redaction.md)
|
|
107
|
+
- [Configuration reference](../reference/config.md)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Metadata redaction
|
|
2
|
+
|
|
3
|
+
Many log payloads encode sensitivity in **sibling fields** rather than key names:
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"name": "email",
|
|
8
|
+
"type": "String",
|
|
9
|
+
"value": "foo.bar@example.com"
|
|
10
|
+
}
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Redacting every `value` field destroys useful debug output. Instead, use a **Schema** (`customObjects`) so `value` is redacted only when `name` matches your Shallow / Deep / Opaque / Remove rules.
|
|
14
|
+
|
|
15
|
+
## Schema matching
|
|
16
|
+
|
|
17
|
+
An object matches a `CustomObject` schema when it contains **every key defined in the schema**. Extra keys on the input are allowed.
|
|
18
|
+
|
|
19
|
+
When multiple schemas match, the one with the **most keys** wins.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { CustomObject, CustomObjectMatchType } from 'field-redactor';
|
|
23
|
+
|
|
24
|
+
const metadataSchema: CustomObject = {
|
|
25
|
+
name: CustomObjectMatchType.Ignore,
|
|
26
|
+
type: CustomObjectMatchType.Ignore,
|
|
27
|
+
value: 'name' // sibling key: test `name`'s value against secretKeys
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Matches: { name: "email", type: "String", value: "foo@bar.com", id: 12 }
|
|
31
|
+
// Does not match: { name: "email", value: "foo@bar.com" } — missing required "type"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Sibling key rules
|
|
35
|
+
|
|
36
|
+
When a schema field is a **string** (e.g. `value: 'name'`), that string names a sibling key. The sibling's **value** is tested against `secretKeys`, `deepSecretKeys`, `fullSecretKeys`, and `deleteSecretKeys`.
|
|
37
|
+
|
|
38
|
+
Falsy sibling values (`""`, `0`, `false`) are evaluated when the sibling key is present.
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { CustomObjectMatchType, FieldRedactor } from 'field-redactor';
|
|
42
|
+
|
|
43
|
+
const fieldRedactor = new FieldRedactor({
|
|
44
|
+
secretKeys: [/email/],
|
|
45
|
+
customObjects: [
|
|
46
|
+
{
|
|
47
|
+
name: CustomObjectMatchType.Ignore,
|
|
48
|
+
type: CustomObjectMatchType.Ignore,
|
|
49
|
+
value: 'name'
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await fieldRedactor.redact({ name: 'email', type: 'String', value: 'foo@bar.com', id: 12 });
|
|
55
|
+
// → value redacted, id unchanged
|
|
56
|
+
|
|
57
|
+
await fieldRedactor.redact({ name: 'traceId', type: 'String', value: 'abc-123', id: 10 });
|
|
58
|
+
// → value unchanged
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Builder with named schemas
|
|
62
|
+
|
|
63
|
+
Name schemas to improve `dryRun` reports:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { CustomObjectMatchType, FieldRedactorConfigBuilder } from 'field-redactor';
|
|
67
|
+
|
|
68
|
+
const redactor = FieldRedactorConfigBuilder.create()
|
|
69
|
+
.shallow(/email/i, /password/i, /phone/i)
|
|
70
|
+
.remove(/authKey/i)
|
|
71
|
+
.schema(
|
|
72
|
+
{ name: CustomObjectMatchType.Ignore, type: CustomObjectMatchType.Ignore, value: 'name' },
|
|
73
|
+
{ name: 'metadata-entry' }
|
|
74
|
+
)
|
|
75
|
+
.buildSafeRedactor();
|
|
76
|
+
|
|
77
|
+
const { report } = redactor.dryRunSync({
|
|
78
|
+
metadata: [{ name: 'email', type: 'String', value: 'alice@example.com' }]
|
|
79
|
+
});
|
|
80
|
+
// report.matchedSchemas → [{ path: 'metadata[0]', schemaIndex: 0, schemaName: 'metadata-entry' }]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Per-field modes (`CustomObjectMatchType`)
|
|
84
|
+
|
|
85
|
+
Inside a schema, use enum values instead of sibling strings:
|
|
86
|
+
|
|
87
|
+
| Enum | Doc label | Effect |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| `Delete` | Remove | Delete the field |
|
|
90
|
+
| `Full` | Opaque | Stringify and redact |
|
|
91
|
+
| `Deep` | Deep | Deep redaction |
|
|
92
|
+
| `Shallow` | Shallow | Redact scalar / primitive arrays |
|
|
93
|
+
| `Pass` | — | Revert to normal traversal for nested content |
|
|
94
|
+
| `Ignore` | — | Skip the field |
|
|
95
|
+
|
|
96
|
+
## Preset: logging metadata
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { FieldRedactor, presets } from 'field-redactor';
|
|
100
|
+
|
|
101
|
+
const redactor = FieldRedactor.createSafe({
|
|
102
|
+
...presets.loggingMetadata(),
|
|
103
|
+
secretKeys: [/email/i, /ssn/i]
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`presets.loggingMetadata()` includes `{ name, type, value }` schema and `deleteSecretKeys: [/authKey/i]`.
|
|
108
|
+
|
|
109
|
+
## Related
|
|
110
|
+
|
|
111
|
+
- [Secret key modes](secret-key-modes.md) — Shallow / Deep / Opaque / Remove
|
|
112
|
+
- [Configuration reference](../reference/config.md) — full schema reference and examples
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Migration: npm 1.2.x → 1.5.0
|
|
2
|
+
|
|
3
|
+
This guide covers upgrading from the last published npm line (**1.2.0**) to **1.5.0**, which bundles **v1.2.1**–**v1.3.0** git work plus Phase 2 DX polish and value-pattern redaction.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install field-redactor@1.5.0
|
|
9
|
+
# or
|
|
10
|
+
yarn add field-redactor@1.5.0
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Breaking or behavior-sensitive changes
|
|
14
|
+
|
|
15
|
+
### 1. Copy-on-write instead of full deep clone
|
|
16
|
+
|
|
17
|
+
`redact()` and `redactSync()` no longer deep-clone the entire input first. Unmodified nested objects may **share references** with the input.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// If tests assert result.child !== input.child for untouched branches, update expectations.
|
|
21
|
+
// To mutate the input in place (old clone-then-mutate semantics on the copy):
|
|
22
|
+
new FieldRedactor({ secretKeys: [/email/], cloneInput: false });
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 2. Relaxed custom object schema matching
|
|
26
|
+
|
|
27
|
+
Schemas match when the input object contains **every** schema key. Extra keys on the input are allowed (previously required an exact key set in some cases).
|
|
28
|
+
|
|
29
|
+
### 3. Configuration warnings
|
|
30
|
+
|
|
31
|
+
`new FieldRedactor()` without explicit rules still redacts all values (legacy default) but now emits warnings. Use:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
FieldRedactor.createSafe({ secretKeys: [/email/i] });
|
|
35
|
+
// or
|
|
36
|
+
FieldRedactorConfigBuilder.create().shallow(/email/i).buildSafeRedactor();
|
|
37
|
+
// or
|
|
38
|
+
new FieldRedactor({ strict: true, secretKeys: [/email/i] });
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Recommended new APIs
|
|
42
|
+
|
|
43
|
+
| Goal | API |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| Avoid accidental full redaction | `FieldRedactor.createSafe()`, `buildSafeRedactor()` |
|
|
46
|
+
| Faster sync pipelines | `redactSync()`, `redactInPlaceSync()` |
|
|
47
|
+
| Tune regexes / schemas | `dryRunSync()` → `report.redactedPaths`, `pathRules` |
|
|
48
|
+
| Start from known fixtures | `presets.applicationLogging()`, `.usePreset()` |
|
|
49
|
+
| PII in free-text values | `valuePatterns` or `.valuePattern()` |
|
|
50
|
+
|
|
51
|
+
## Step-by-step upgrade
|
|
52
|
+
|
|
53
|
+
### Step 1 — Audit current config
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { validateFieldRedactorConfig } from 'field-redactor';
|
|
57
|
+
|
|
58
|
+
const warnings = validateFieldRedactorConfig(yourConfig);
|
|
59
|
+
console.log(warnings);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Step 2 — Preview on real payloads
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const redactor = FieldRedactor.createSafe(yourConfig);
|
|
66
|
+
const { result, report } = redactor.dryRunSync(samplePayload);
|
|
67
|
+
// Review report.pathRules, report.matchedSchemas, report.deletedPaths
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Step 3 — Switch hot paths to sync (optional)
|
|
71
|
+
|
|
72
|
+
If you use the default or `syncRedactor` and do not need async `redactor`:
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// Before
|
|
76
|
+
const out = await redactor.redact(payload);
|
|
77
|
+
|
|
78
|
+
// After
|
|
79
|
+
const out = redactor.redactSync(payload);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Step 4 — Add value patterns (optional)
|
|
83
|
+
|
|
84
|
+
Only when sensitivity is in **values**, not key names:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
FieldRedactorConfigBuilder.create()
|
|
88
|
+
.shallow(/email/i, /password/i)
|
|
89
|
+
.valuePattern(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/)
|
|
90
|
+
.buildSafeRedactor();
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Value patterns run at **lowest precedence** — after schema and all key-based rules.
|
|
94
|
+
|
|
95
|
+
## Fixes you get for free
|
|
96
|
+
|
|
97
|
+
- **Falsy sibling keys** — `metadata: { name: 'email', value: '' }` redacts correctly when the sibling is present.
|
|
98
|
+
- **`ignoreBooleans` default** — documented as `false` (booleans are redacted unless you opt out).
|
|
99
|
+
- **Exported errors** — `FieldRedactorError`, `FieldRedactorConfigurationError`.
|
|
100
|
+
- **Stronger typing** — `JsonValue`, `RedactableInput`, generic `redact<T>()`.
|
|
101
|
+
|
|
102
|
+
## Further reading
|
|
103
|
+
|
|
104
|
+
- [Release notes v1.5.0](../release-notes/v1.5.0.md)
|
|
105
|
+
- [Release notes v1.3.0](../release-notes/v1.3.0.md) — detailed 1.3.0 feature breakdown
|
|
106
|
+
- [Anti-patterns](anti-patterns.md)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Secret key modes
|
|
2
|
+
|
|
3
|
+
FieldRedactor applies **Shallow**, **Deep**, **Opaque**, and **Remove** rules via regex key matching. Throughout the docs, these are the conceptual names; the config fields are `secretKeys`, `deepSecretKeys`, `fullSecretKeys`, and `deleteSecretKeys`.
|
|
4
|
+
|
|
5
|
+
**Precedence** (highest wins): Schema (`customObjects`) → Opaque → Deep → Remove → Shallow → Value-pattern (`valuePatterns`)
|
|
6
|
+
|
|
7
|
+
## Cheat sheet
|
|
8
|
+
|
|
9
|
+
Same input for every row:
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"username": "alice",
|
|
14
|
+
"contactInfo": { "email": "alice@example.com", "city": "NYC" },
|
|
15
|
+
"authKey": "secret-token"
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
| Mode | Config field | Example | Result |
|
|
20
|
+
| --- | --- | --- | --- |
|
|
21
|
+
| **Shallow** | `secretKeys` | `[/email/]` | `contactInfo.email` → `"REDACTED"`; `city`, `username` unchanged |
|
|
22
|
+
| **Deep** | `deepSecretKeys` | `[/contactInfo/]` | All primitives inside `contactInfo` redacted |
|
|
23
|
+
| **Opaque** | `fullSecretKeys` | `[/contactInfo/]` | Entire `contactInfo` replaced with `"REDACTED"` |
|
|
24
|
+
| **Remove** | `deleteSecretKeys` | `[/authKey/]` | `authKey` key removed from output |
|
|
25
|
+
|
|
26
|
+
### Builder API
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { FieldRedactorConfigBuilder } from 'field-redactor';
|
|
30
|
+
|
|
31
|
+
const config = FieldRedactorConfigBuilder.create()
|
|
32
|
+
.shallow(/email/i)
|
|
33
|
+
.deep(/contactInfo/i)
|
|
34
|
+
.opaque(/rawPayload/i)
|
|
35
|
+
.remove(/authKey/i)
|
|
36
|
+
.build();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Shallow (`secretKeys`)
|
|
40
|
+
|
|
41
|
+
Redact matching keys' scalar values without deep-walking child objects. If **no** redaction rules of any type are configured, every value is treated as Shallow (all fields redacted).
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { FieldRedactor } from 'field-redactor';
|
|
45
|
+
|
|
46
|
+
const fieldRedactor = new FieldRedactor({
|
|
47
|
+
secretKeys: [/email/i, /name/i, /userid/i]
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
await fieldRedactor.redact({
|
|
51
|
+
userId: 271,
|
|
52
|
+
contactInfo: { email: 'foo@bar.com', firstName: 'Foo', Salutation: 'Mr.' }
|
|
53
|
+
});
|
|
54
|
+
// → userId, email, firstName redacted; Salutation unchanged
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Nested objects keep normal traversal rules; primitives inside arrays at matching keys are redacted.
|
|
58
|
+
|
|
59
|
+
## Deep (`deepSecretKeys`)
|
|
60
|
+
|
|
61
|
+
Redact matching keys and **all** descendant primitives. Higher precedence than Shallow.
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
const fieldRedactor = new FieldRedactor({
|
|
65
|
+
deepSecretKeys: [/contactInfo/i]
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await fieldRedactor.redact({
|
|
69
|
+
contactInfo: {
|
|
70
|
+
email: 'foo@bar.com',
|
|
71
|
+
lastUpdatedBy: { id: 1 }
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
// → email, id, and other primitives under contactInfo are redacted
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Opaque (`fullSecretKeys`)
|
|
78
|
+
|
|
79
|
+
Stringify the entire value at matching keys, then redact. Typical for objects and arrays you want to hide completely.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
const fieldRedactor = new FieldRedactor({
|
|
83
|
+
fullSecretKeys: [/contactInfo/i]
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
await fieldRedactor.redact({
|
|
87
|
+
contactInfo: { email: 'foo@bar.com', firstName: 'Foo' }
|
|
88
|
+
});
|
|
89
|
+
// → { contactInfo: "REDACTED" }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Remove (`deleteSecretKeys`)
|
|
93
|
+
|
|
94
|
+
Delete matching keys from the output.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
const fieldRedactor = new FieldRedactor({
|
|
98
|
+
deleteSecretKeys: [/authKey/i]
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
await fieldRedactor.redact({ userId: 271, appAuthKey: '12345' });
|
|
102
|
+
// → { userId: 271 }
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Related
|
|
106
|
+
|
|
107
|
+
- [Metadata redaction](metadata-redaction.md) — when sensitivity depends on sibling fields
|
|
108
|
+
- [Configuration reference](../reference/config.md) — all options and API details
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Value-pattern redaction
|
|
2
|
+
|
|
3
|
+
Opt-in redaction for scalar fields whose **value** matches a regex, regardless of key name. Use this when PII appears in free-text fields (`description`, `message`, `body`) that are not named like the data they contain.
|
|
4
|
+
|
|
5
|
+
Value-pattern rules are **lowest precedence**: they run only when Shallow, Deep, Opaque, Remove, and Schema rules leave the field unchanged.
|
|
6
|
+
|
|
7
|
+
## When to use
|
|
8
|
+
|
|
9
|
+
| Situation | Approach |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| Key name identifies sensitivity (`password`, `email`) | Shallow / Deep / Opaque / Remove |
|
|
12
|
+
| Sensitivity depends on a sibling (`{ name, value }`) | Schema |
|
|
13
|
+
| PII may appear in arbitrary string fields | **Value-pattern** (`valuePatterns`) |
|
|
14
|
+
|
|
15
|
+
## Configuration
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { FieldRedactor, FieldRedactorConfigBuilder } from 'field-redactor';
|
|
19
|
+
|
|
20
|
+
const emailPattern = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/;
|
|
21
|
+
|
|
22
|
+
// Plain config — valuePatterns-only is valid with createSafe()
|
|
23
|
+
const redactor = FieldRedactor.createSafe({
|
|
24
|
+
valuePatterns: [emailPattern]
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Builder
|
|
28
|
+
const built = FieldRedactorConfigBuilder.create()
|
|
29
|
+
.valuePattern(emailPattern, /\b\d{3}-\d{2}-\d{4}\b/) // SSN-shaped values
|
|
30
|
+
.shallow(/password/i) // key rules still apply
|
|
31
|
+
.buildSafeRedactor();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Behavior
|
|
35
|
+
|
|
36
|
+
- Matches **strings** and **numbers** (tested via `String(value)`). Booleans, `null`, and `undefined` are skipped.
|
|
37
|
+
- Does not run inside Deep or Opaque subtrees that already redact all primitives.
|
|
38
|
+
- Combine with key rules for defense in depth: key names catch structured fields; value patterns catch embedded PII in free text.
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
const input = {
|
|
42
|
+
email: 'alice@example.com', // shallow key rule
|
|
43
|
+
description: 'Email alice@example.com' // value pattern
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const { report } = redactor.dryRunSync(input);
|
|
47
|
+
// pathRules → shallow on `email`, value on `description`
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## dryRun attribution
|
|
51
|
+
|
|
52
|
+
Redactions from value patterns appear in `report.pathRules` with `rule: 'value'` and the matching regex in `pattern`.
|
|
53
|
+
|
|
54
|
+
## Related
|
|
55
|
+
|
|
56
|
+
- [Secret key modes](secret-key-modes.md) — key-name rules
|
|
57
|
+
- [Anti-patterns](anti-patterns.md) — do not confuse key rules with value detection
|
|
58
|
+
- [Configuration reference](../reference/config.md)
|