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.
Files changed (123) hide show
  1. package/.github/workflows/ci.yml +38 -0
  2. package/CHANGELOG.md +156 -0
  3. package/README.md +64 -670
  4. package/dist/configValidator.d.ts +10 -0
  5. package/dist/configValidator.d.ts.map +1 -0
  6. package/dist/configValidator.js +94 -0
  7. package/dist/configValidator.js.map +1 -0
  8. package/dist/copyOnWriteHelpers.d.ts +20 -0
  9. package/dist/copyOnWriteHelpers.d.ts.map +1 -0
  10. package/dist/copyOnWriteHelpers.js +58 -0
  11. package/dist/copyOnWriteHelpers.js.map +1 -0
  12. package/dist/customObjectManager.d.ts +13 -7
  13. package/dist/customObjectManager.d.ts.map +1 -1
  14. package/dist/customObjectManager.js +30 -29
  15. package/dist/customObjectManager.js.map +1 -1
  16. package/dist/dryRun.d.ts +8 -0
  17. package/dist/dryRun.d.ts.map +1 -0
  18. package/dist/dryRun.js +75 -0
  19. package/dist/dryRun.js.map +1 -0
  20. package/dist/dryRunAttribution.d.ts +8 -0
  21. package/dist/dryRunAttribution.d.ts.map +1 -0
  22. package/dist/dryRunAttribution.js +106 -0
  23. package/dist/dryRunAttribution.js.map +1 -0
  24. package/dist/fieldRedactor.d.ts +43 -5
  25. package/dist/fieldRedactor.d.ts.map +1 -1
  26. package/dist/fieldRedactor.js +124 -24
  27. package/dist/fieldRedactor.js.map +1 -1
  28. package/dist/fieldRedactorConfigBuilder.d.ts +49 -0
  29. package/dist/fieldRedactorConfigBuilder.d.ts.map +1 -0
  30. package/dist/fieldRedactorConfigBuilder.js +98 -0
  31. package/dist/fieldRedactorConfigBuilder.js.map +1 -0
  32. package/dist/fieldRedactorDeps.d.ts +17 -0
  33. package/dist/fieldRedactorDeps.d.ts.map +1 -0
  34. package/dist/fieldRedactorDeps.js +41 -0
  35. package/dist/fieldRedactorDeps.js.map +1 -0
  36. package/dist/index.d.ts +7 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +14 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/jsonWalk.d.ts +11 -0
  41. package/dist/jsonWalk.d.ts.map +1 -0
  42. package/dist/jsonWalk.js +86 -0
  43. package/dist/jsonWalk.js.map +1 -0
  44. package/dist/objectRedactor.d.ts +9 -34
  45. package/dist/objectRedactor.d.ts.map +1 -1
  46. package/dist/objectRedactor.js +11 -298
  47. package/dist/objectRedactor.js.map +1 -1
  48. package/dist/objectRedactorCow.d.ts +35 -0
  49. package/dist/objectRedactorCow.d.ts.map +1 -0
  50. package/dist/objectRedactorCow.js +300 -0
  51. package/dist/objectRedactorCow.js.map +1 -0
  52. package/dist/objectRedactorCustomObject.d.ts +49 -0
  53. package/dist/objectRedactorCustomObject.d.ts.map +1 -0
  54. package/dist/objectRedactorCustomObject.js +138 -0
  55. package/dist/objectRedactorCustomObject.js.map +1 -0
  56. package/dist/objectRedactorHelpers.d.ts +9 -0
  57. package/dist/objectRedactorHelpers.d.ts.map +1 -0
  58. package/dist/objectRedactorHelpers.js +58 -0
  59. package/dist/objectRedactorHelpers.js.map +1 -0
  60. package/dist/objectRedactorMutation.d.ts +10 -0
  61. package/dist/objectRedactorMutation.d.ts.map +1 -0
  62. package/dist/objectRedactorMutation.js +89 -0
  63. package/dist/objectRedactorMutation.js.map +1 -0
  64. package/dist/objectRedactorSync.d.ts +2 -0
  65. package/dist/objectRedactorSync.d.ts.map +1 -0
  66. package/dist/objectRedactorSync.js +7 -0
  67. package/dist/objectRedactorSync.js.map +1 -0
  68. package/dist/objectRedactorTraversal.d.ts +57 -0
  69. package/dist/objectRedactorTraversal.d.ts.map +1 -0
  70. package/dist/objectRedactorTraversal.js +576 -0
  71. package/dist/objectRedactorTraversal.js.map +1 -0
  72. package/dist/presets.d.ts +22 -0
  73. package/dist/presets.d.ts.map +1 -0
  74. package/dist/presets.js +50 -0
  75. package/dist/presets.js.map +1 -0
  76. package/dist/primitiveRedactor.d.ts +13 -8
  77. package/dist/primitiveRedactor.d.ts.map +1 -1
  78. package/dist/primitiveRedactor.js +32 -27
  79. package/dist/primitiveRedactor.js.map +1 -1
  80. package/dist/redactionRules.d.ts +23 -0
  81. package/dist/redactionRules.d.ts.map +1 -0
  82. package/dist/redactionRules.js +76 -0
  83. package/dist/redactionRules.js.map +1 -0
  84. package/dist/regexUtils.d.ts +3 -0
  85. package/dist/regexUtils.d.ts.map +1 -0
  86. package/dist/regexUtils.js +8 -0
  87. package/dist/regexUtils.js.map +1 -0
  88. package/dist/secretManager.d.ts +12 -6
  89. package/dist/secretManager.d.ts.map +1 -1
  90. package/dist/secretManager.js +40 -14
  91. package/dist/secretManager.js.map +1 -1
  92. package/dist/types.d.ts +103 -2
  93. package/dist/types.d.ts.map +1 -1
  94. package/dist/types.js +7 -1
  95. package/dist/types.js.map +1 -1
  96. package/dist/valuePatternMatcher.d.ts +15 -0
  97. package/dist/valuePatternMatcher.d.ts.map +1 -0
  98. package/dist/valuePatternMatcher.js +30 -0
  99. package/dist/valuePatternMatcher.js.map +1 -0
  100. package/docs/guides/anti-patterns.md +107 -0
  101. package/docs/guides/metadata-redaction.md +112 -0
  102. package/docs/guides/migration-1.2-to-1.5.md +106 -0
  103. package/docs/guides/secret-key-modes.md +108 -0
  104. package/docs/guides/value-pattern-redaction.md +58 -0
  105. package/docs/reference/config.md +135 -0
  106. package/docs/release-notes/2.0.0.md +19 -0
  107. package/docs/release-notes/2.1.0.md +17 -0
  108. package/docs/release-notes/2.2.0.md +16 -0
  109. package/docs/release-notes/2.3.0.md +17 -0
  110. package/docs/release-notes/2.3.1.md +17 -0
  111. package/docs/release-notes/2.4.0.md +30 -0
  112. package/docs/release-notes/2.5.0.md +33 -0
  113. package/docs/release-notes/2.5.1.md +17 -0
  114. package/docs/release-notes/README.md +30 -0
  115. package/docs/release-notes/v1.0.0.md +17 -0
  116. package/docs/release-notes/v1.1.0.md +18 -0
  117. package/docs/release-notes/v1.2.0.md +18 -0
  118. package/docs/release-notes/v1.2.1.md +16 -0
  119. package/docs/release-notes/v1.2.2.md +19 -0
  120. package/docs/release-notes/v1.3.0.md +112 -0
  121. package/docs/release-notes/v1.5.0.md +88 -0
  122. package/jest.config.js +4 -1
  123. 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":";;;AAEA,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"}
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)