@hackylabs/deep-redact 3.0.5 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +229 -106
- package/dist/adapters/console/index.cjs +74 -0
- package/dist/adapters/console/index.d.cts +22 -0
- package/dist/adapters/console/index.d.ts +22 -0
- package/dist/adapters/console/index.js +73 -0
- package/dist/index.cjs +2782 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +2780 -55
- package/dist/node-console-sink-BnRUkAAr.cjs +19 -0
- package/dist/node-console-sink-DaQleNZ8.js +14 -0
- package/dist/public-Da0aARA9.d.cts +127 -0
- package/dist/public-Dw4ycNzO.d.ts +127 -0
- package/package.json +68 -106
- package/dist/index.mjs +0 -40
- package/dist/types/index.d.ts +0 -29
- package/dist/types/types.d.ts +0 -224
- package/dist/types/utils/TransformerRegistry.d.ts +0 -52
- package/dist/types/utils/index.d.ts +0 -131
- package/dist/types/utils/standardTransformers/bigint.d.ts +0 -2
- package/dist/types/utils/standardTransformers/date.d.ts +0 -2
- package/dist/types/utils/standardTransformers/error.d.ts +0 -2
- package/dist/types/utils/standardTransformers/index.d.ts +0 -9
- package/dist/types/utils/standardTransformers/map.d.ts +0 -2
- package/dist/types/utils/standardTransformers/regex.d.ts +0 -2
- package/dist/types/utils/standardTransformers/set.d.ts +0 -2
- package/dist/types/utils/standardTransformers/url.d.ts +0 -2
- package/dist/types.js +0 -2
- package/dist/types.mjs +0 -1
- package/dist/utils/TransformerRegistry.js +0 -100
- package/dist/utils/TransformerRegistry.mjs +0 -94
- package/dist/utils/index.js +0 -471
- package/dist/utils/index.mjs +0 -467
- package/dist/utils/standardTransformers/bigint.js +0 -10
- package/dist/utils/standardTransformers/bigint.mjs +0 -6
- package/dist/utils/standardTransformers/date.js +0 -9
- package/dist/utils/standardTransformers/date.mjs +0 -5
- package/dist/utils/standardTransformers/error.js +0 -16
- package/dist/utils/standardTransformers/error.mjs +0 -12
- package/dist/utils/standardTransformers/index.js +0 -38
- package/dist/utils/standardTransformers/index.mjs +0 -35
- package/dist/utils/standardTransformers/map.js +0 -9
- package/dist/utils/standardTransformers/map.mjs +0 -5
- package/dist/utils/standardTransformers/regex.js +0 -15
- package/dist/utils/standardTransformers/regex.mjs +0 -11
- package/dist/utils/standardTransformers/set.js +0 -9
- package/dist/utils/standardTransformers/set.mjs +0 -5
- package/dist/utils/standardTransformers/url.js +0 -9
- package/dist/utils/standardTransformers/url.mjs +0 -5
package/README.md
CHANGED
|
@@ -3,128 +3,251 @@
|
|
|
3
3
|
[](https://badge.fury.io/js/@hackylabs%2Fdeep-redact)
|
|
4
4
|
[](https://github.com/hackylabs/deep-redact/blob/main/LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
sensitive information from strings and objects. It is designed to be used in a production environment where sensitive
|
|
8
|
-
information needs to be redacted from logs, error messages, files, and other outputs. Supporting both strings and objects
|
|
9
|
-
or a mix of both, Deep Redact can be used to redact sensitive information from more data structures than any other
|
|
10
|
-
redaction library. Even partially redacting sensitive information from strings is supported, by way of custom regex
|
|
11
|
-
patterns and replacers.
|
|
6
|
+
@hackylabs/deep-redact is a rule-driven redaction library for Node.js and Deno. It lets you express what to redact — a key name, a path, a regex, or a substring — and the runtime locates and replaces it everywhere in the payload without requiring exhaustive path enumeration. Policies are compiled once at initialisation and reused across every request, making it suitable for high-throughput production use.
|
|
12
7
|
|
|
13
|
-
|
|
14
|
-
possible while still being easy to use and configure.
|
|
15
|
-
|
|
16
|
-
Supporting both CommonJS and ESM, with named and default exports, Deep Redact is designed to be versatile and easy to
|
|
17
|
-
use in any modern JavaScript or TypeScript project in Node or the browser.
|
|
8
|
+
Libraries like `fast-redact` require an explicit path for every location where a sensitive field might appear (`a.b.password`, `a.c.password`, …). Deep Redact inverts this: a single `keys: ['password']` rule targets every `password` field at any depth. It also adds capabilities that path-enumeration libraries cannot offer, such as partial string redaction (targeting a sensitive pattern inside a larger value) and wildcard path selectors for repeated structures.
|
|
18
9
|
|
|
19
10
|
[](https://ko-fi.com/hackylabs)
|
|
20
11
|
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Depth-agnostic key rules** — redact any field by name at any nesting depth; no path enumeration required
|
|
15
|
+
- **Exact and structured path targeting** — pin redaction to a specific location when precision is needed
|
|
16
|
+
- **Regex property matching** — match field names by regular expression
|
|
17
|
+
- **Substring targeting** — redact a sensitive string that appears inside a larger value, not just whole-value replacement
|
|
18
|
+
- **Wildcard and exclusion path selectors** — handle repeated structures at virtually any depth (`['addresses.*', { ignore: 'country' }]`) without listing every index
|
|
19
|
+
- **Structured and serialised output** — return a live object graph or a guaranteed-JSON-safe string (`serialise: true`)
|
|
20
|
+
- **Built-in and custom transformers** — override how specific runtime types are represented in serialised output
|
|
21
|
+
- **Built-in security** — prevent prototype pollution and DoS by memory exhaustion
|
|
22
|
+
- **Console adapter** — optional `console.*` redaction via an explicit adapter
|
|
23
|
+
|
|
24
|
+
## Contributor Baseline
|
|
25
|
+
|
|
26
|
+
- Node `24.14.1`
|
|
27
|
+
- `pnpm@10.33.0`
|
|
28
|
+
- `tsdown`
|
|
29
|
+
- `Vitest`
|
|
30
|
+
- `ESLint`
|
|
31
|
+
|
|
21
32
|
## Installation
|
|
22
33
|
|
|
23
|
-
```
|
|
34
|
+
```sh
|
|
24
35
|
npm install @hackylabs/deep-redact
|
|
36
|
+
pnpm add @hackylabs/deep-redact
|
|
37
|
+
yarn add @hackylabs/deep-redact
|
|
38
|
+
bun add @hackylabs/deep-redact
|
|
25
39
|
```
|
|
26
40
|
|
|
27
|
-
|
|
41
|
+
**Deno** — add the package to your import map (`deno.json`):
|
|
28
42
|
|
|
29
|
-
|
|
30
|
-
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"imports": {
|
|
46
|
+
"@hackylabs/deep-redact": "npm:@hackylabs/deep-redact@4.0.1"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
31
50
|
|
|
32
|
-
|
|
33
|
-
// ./src/example.ts
|
|
34
|
-
import {DeepRedact} from '@hackylabs/deep-redact'; // If you're using CommonJS, import with require('@hackylabs/deep-redact') instead. Both CommonJS and ESM support named and default imports.
|
|
51
|
+
## Quick Start
|
|
35
52
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
})
|
|
53
|
+
```ts
|
|
54
|
+
import { deepRedact } from '@hackylabs/deep-redact'
|
|
39
55
|
|
|
40
|
-
const
|
|
41
|
-
keepThis: 'This is fine',
|
|
42
|
-
sensitive: 'This is not fine',
|
|
43
|
-
user: {
|
|
44
|
-
id: 1,
|
|
45
|
-
password: '<h1><strong>Password</strong></h1>',
|
|
46
|
-
firstName: 'John',
|
|
47
|
-
}
|
|
48
|
-
}
|
|
56
|
+
const redact = deepRedact({ keys: ['password', 'token'] })
|
|
49
57
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// {
|
|
53
|
-
// keepThis: 'This is fine',
|
|
54
|
-
// sensitive: '[REDACTED]',
|
|
55
|
-
// user: {
|
|
56
|
-
// id: 1,
|
|
57
|
-
// password: '[REDACTED]',
|
|
58
|
-
// firstName: '[REDACTED]'
|
|
59
|
-
// }
|
|
60
|
-
// }
|
|
61
|
-
|
|
62
|
-
const strRedaction = new DeepRedact({
|
|
63
|
-
stringTests: [
|
|
64
|
-
{
|
|
65
|
-
pattern: /<(email|password)>([^<]+)<\/\1>/gi,
|
|
66
|
-
replacer: (value: string, pattern: RegExp) => value.replace(pattern, '<$1>[REDACTED]</$1>'),
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
// Partially redact sensitive information from a string
|
|
72
|
-
strRedaction.redact('<email>someone@somewhere.com</email><keepThis>This is fine</keepThis><password>secret</password>')
|
|
73
|
-
// '<email>[REDACTED]</email><keepThis>This is fine</keepThis><password>[REDACTED]</password>'
|
|
58
|
+
redact({ user: { password: 'secret', safe: 'keep this' }, token: 'abc123', id: 1 })
|
|
59
|
+
// { user: { password: '[REDACTED]', safe: 'keep this' }, token: '[REDACTED]', id: 1 }
|
|
74
60
|
```
|
|
75
61
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
62
|
+
## Benchmarks
|
|
63
|
+
|
|
64
|
+
Generated from committed artefacts in `test/artefacts/benchmarks/speed/`. Node.js 24 LTS, Apple M-series. Steady-state after 10,000 warmup iterations.
|
|
65
|
+
|
|
66
|
+
### TLDR Headline
|
|
67
|
+
|
|
68
|
+
> **If raw throughput is your primary constraint and you can accept exhaustive path enumeration, use [`fast-redact`](https://www.npmjs.com/package/fast-redact) instead.** It is purpose-built for maximum JSON-string output speed and will outperform any rule-driven library in that narrow scenario. Use Deep Redact when you need depth-agnostic targeting, wildcard paths, substring redaction, or built-in security guarantees.
|
|
69
|
+
|
|
70
|
+
If you choose fast-redact instead, be aware of the trade-offs:
|
|
71
|
+
|
|
72
|
+
- **No depth-agnostic key rules** — every sensitive path must be enumerated explicitly
|
|
73
|
+
- **No double-wildcard (`**`) path selectors** — you cannot target `records.**.email` without listing every depth
|
|
74
|
+
- **No substring targeting** — whole-value replacement only; partial string redaction is not supported
|
|
75
|
+
- **No serialisation-safe transformer pipeline** — no built-in handling for `BigInt`, `Date`, `Map`, `Set`, `Error`, `RegExp`, or `URL` values
|
|
76
|
+
- **No prototype pollution protection** — no guard against `__proto__` and `constructor.prototype` injection
|
|
77
|
+
- **No configurable depth or node limits** — no protection against DoS via deeply nested or excessively large payloads
|
|
78
|
+
|
|
79
|
+
### Structured output (`serialise: false`)
|
|
80
|
+
|
|
81
|
+
v4, v3, and fast-redact all return a plain JavaScript object.
|
|
82
|
+
|
|
83
|
+

|
|
84
|
+
|
|
85
|
+
_† fast-redact is a third-party library, not a deep-redact version. It is shown as a throughput reference; its feature set and guarantees differ from deep-redact's, so it is not a like-for-like comparison._
|
|
86
|
+
|
|
87
|
+
v4 is **~17× faster** than v3 on path-based workloads and **~10× faster** on wildcard workloads.
|
|
88
|
+
|
|
89
|
+
### Serialised output (`serialise: true`)
|
|
90
|
+
|
|
91
|
+
All four solutions return a JSON string.
|
|
92
|
+
|
|
93
|
+

|
|
94
|
+
|
|
95
|
+
_† fast-redact is a third-party library; json-stringify-regex is a naive native approach (`JSON.stringify(value).replace(pattern, replacement)`), not a library. Neither performs deep-redact's structured redaction or offers its guarantees, so both are shown as throughput references rather than like-for-like comparisons._
|
|
96
|
+
|
|
97
|
+
v4 remains faster than v3 in serialised mode. fast-redact and json-stringify-regex have a throughput advantage because their output path is oriented entirely toward string production.
|
|
98
|
+
|
|
99
|
+
Against deep-redact v2 the serialised picture is mixed: v4 is roughly at parity on path-based workloads but slower on breadth-heavy (wildcard) ones. That gap is the cost of safety v2 does not provide — under `serialise: true`, v4 runs the type transformers that make `BigInt`, `Date`, `Map`, `Set`, `Error`, `RegExp`, and `URL` values JSON-safe, and it detects and neutralises circular references instead of throwing on them. (Node and depth budgeting via `maxNodes`/`maxDepth` is opt-in and unlimited by default, so it adds nothing unless you enable it.)
|
|
100
|
+
|
|
101
|
+
These safety passes are intrinsic to `serialise: true` and cannot be switched off individually. If you do not need those guarantees and want to match v2's throughput (or better), set `serialise: false` and run your own `JSON.stringify`: the structured-output path skips the transformer and circular-reference passes entirely. This restores v2's trade-offs too — your `JSON.stringify` will throw on `BigInt` and circular references, `Map`/`Set` serialise as `{}`, and `undefined` is dropped.
|
|
102
|
+
|
|
103
|
+
Full speed and resource benchmark results: [`docs/benchmarks/speed-results.md`](docs/benchmarks/speed-results.md) and [`docs/benchmarks/resource-results.md`](docs/benchmarks/resource-results.md).
|
|
93
104
|
|
|
94
105
|
## Configuration
|
|
95
106
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
|
101
|
-
|
|
102
|
-
|
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
|
116
|
-
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
|
128
|
-
|
|
|
129
|
-
|
|
|
130
|
-
|
|
|
107
|
+
`deepRedact(options)` accepts a single options object. All options are optional.
|
|
108
|
+
|
|
109
|
+
### Top-level options
|
|
110
|
+
|
|
111
|
+
| Option | Type | Default | Required | Description |
|
|
112
|
+
|--------|------|---------|----------|-------------|
|
|
113
|
+
| `keys` | `KeySelector[]` | `[]` | No | Redact any field matching a listed key, at any depth |
|
|
114
|
+
| `paths` | `PathEntry[]` | `[]` | No | Redact fields at specific paths; supports exact strings, structured paths, wildcards, and exclusion selectors |
|
|
115
|
+
| `stringTests` | `StringTest[]` | `[]` | No | Redact matching patterns inside string values |
|
|
116
|
+
| `censor` | `string \| function` | `'[REDACTED]'` | No | Replacement value, or a function `(value, context) => replacement` |
|
|
117
|
+
| `serialise` | `boolean \| function` | `false` | No | `true` returns a JSON-safe string; a function receives the safe graph and may return any string |
|
|
118
|
+
| `remove` | `boolean` | `false` | No | Remove matched keys from the output instead of replacing their values |
|
|
119
|
+
| `retainStructure` | `boolean` | `false` | No | Keep descendant nodes traversable for lower-precedence rules when a parent is matched |
|
|
120
|
+
| `replaceStringByLength` | `boolean` | `false` | No | Repeat the `censor` string to match the character length of the redacted value |
|
|
121
|
+
| `types` | `ValueTypeName[]` | `['string']` | No | `typeof` categories eligible for redaction; a matched key whose value's type is not listed is left untouched |
|
|
122
|
+
| `fuzzyKeyMatch` | `boolean` | `false` | No | Match when the configured key is a substring of the field name (e.g. `'pass'` matches `'password'`) |
|
|
123
|
+
| `caseSensitiveKeyMatch` | `boolean` | `true` | No | When `false`, normalises key names (strips non-word characters, lowercases) before comparing |
|
|
124
|
+
| `maxDepth` | `number` | unlimited | No | Stop traversal beyond this nesting depth; guards against deeply nested payload DoS |
|
|
125
|
+
| `maxNodes` | `number` | unlimited | No | Stop traversal after this many nodes; guards against excessively large payload DoS |
|
|
126
|
+
| `transformers` | `TransformersOption` | built-in defaults | No | Override how runtime types are represented in serialised output |
|
|
127
|
+
| `diagnostics` | `DiagnosticsOptions` | — | No | Receive a structured event when a value is degraded to `[UNSUPPORTED]` during serialisation |
|
|
128
|
+
|
|
129
|
+
`ValueTypeName` is one of: `'string'`, `'number'`, `'bigint'`, `'boolean'`, `'object'`, `'function'`, `'symbol'`, `'undefined'`.
|
|
130
|
+
|
|
131
|
+
### `KeySelector`
|
|
132
|
+
|
|
133
|
+
Each entry in `keys` may be a plain `string`, a `RegExp`, or a `KeyRule` object for per-key overrides.
|
|
134
|
+
|
|
135
|
+
| Field | Type | Default | Required | Description |
|
|
136
|
+
|-------|------|---------|----------|-------------|
|
|
137
|
+
| `key` | `string \| RegExp` | — | Yes | The key to match |
|
|
138
|
+
| `censor` | `string \| function` | top-level `censor` | No | Override the censor for this key only |
|
|
139
|
+
| `remove` | `boolean` | top-level `remove` | No | Override remove for this key only |
|
|
140
|
+
| `retainStructure` | `boolean` | top-level `retainStructure` | No | Override retainStructure for this key only |
|
|
141
|
+
| `replaceStringByLength` | `boolean` | top-level `replaceStringByLength` | No | Override replaceStringByLength for this key only |
|
|
142
|
+
| `fuzzyKeyMatch` | `boolean` | top-level `fuzzyKeyMatch` | No | Override fuzzyKeyMatch for this key only |
|
|
143
|
+
| `caseSensitiveKeyMatch` | `boolean` | top-level `caseSensitiveKeyMatch` | No | Override caseSensitiveKeyMatch for this key only |
|
|
144
|
+
|
|
145
|
+
### `PathEntry`
|
|
146
|
+
|
|
147
|
+
Each entry in `paths` may be a plain dot-notation string (e.g. `'user.profile.ssn'`), a structured path array, or a `PathRule` object for per-path overrides.
|
|
148
|
+
|
|
149
|
+
Structured path arrays may contain:
|
|
150
|
+
|
|
151
|
+
| Segment type | Example | Matches |
|
|
152
|
+
|---|---|---|
|
|
153
|
+
| `string` or `number` | `'password'` | Exact key or index |
|
|
154
|
+
| `RegExp` | `/^pass/i` | Keys matching the regex |
|
|
155
|
+
| `{ any: true }` | — | Any single key (`*`) |
|
|
156
|
+
| `{ anyDepth: true }` | — | Zero or more keys at any depth (`**`) |
|
|
157
|
+
| `{ ignore: string \| number \| RegExp }` | `{ ignore: 'country' }` | Excludes a key from a surrounding wildcard |
|
|
158
|
+
|
|
159
|
+
| Field | Type | Default | Required | Description |
|
|
160
|
+
|-------|------|---------|----------|-------------|
|
|
161
|
+
| `path` | `string \| PathSegments[]` | — | Yes | The path to match |
|
|
162
|
+
| `censor` | `string \| function` | top-level `censor` | No | Override the censor for this path only |
|
|
163
|
+
| `remove` | `boolean` | top-level `remove` | No | Override remove for this path only |
|
|
164
|
+
| `retainStructure` | `boolean` | top-level `retainStructure` | No | Override retainStructure for this path only |
|
|
165
|
+
| `replaceStringByLength` | `boolean` | top-level `replaceStringByLength` | No | Override replaceStringByLength for this path only |
|
|
166
|
+
|
|
167
|
+
### `StringTest`
|
|
168
|
+
|
|
169
|
+
Each entry in `stringTests` may be a bare `RegExp` (whole-value replacement with `censor`) or a `SubstringRule` for partial replacement.
|
|
170
|
+
|
|
171
|
+
| Field | Type | Required | Description |
|
|
172
|
+
|-------|------|----------|-------------|
|
|
173
|
+
| `pattern` | `RegExp` | Yes | Regular expression tested against string values |
|
|
174
|
+
| `replacer` | `(value: string, pattern: RegExp) => string` | Yes | Returns the redacted string; called only when `pattern` matches |
|
|
175
|
+
|
|
176
|
+
### `TransformersOption`
|
|
177
|
+
|
|
178
|
+
Transformers control how non-JSON-safe runtime types are represented when `serialise: true`. Each transformer is a function `(value: unknown) => unknown` that either returns a transformed value or returns the value unchanged to pass to the next transformer in the chain.
|
|
179
|
+
|
|
180
|
+
| Field | Type | Description |
|
|
181
|
+
|-------|------|-------------|
|
|
182
|
+
| `byType.bigint` | `Transformer[]` | Applied to `BigInt` values (does not consult `byType.object`) |
|
|
183
|
+
| `byType.object` | `Transformer[]` | Applied first for built-in constructor types and unknown object types |
|
|
184
|
+
| `byConstructor.Date` | `Transformer[]` | Applied to `Date` instances |
|
|
185
|
+
| `byConstructor.Error` | `Transformer[]` | Applied to `Error` instances |
|
|
186
|
+
| `byConstructor.Map` | `Transformer[]` | Applied to `Map` instances |
|
|
187
|
+
| `byConstructor.RegExp` | `Transformer[]` | Applied to `RegExp` instances |
|
|
188
|
+
| `byConstructor.Set` | `Transformer[]` | Applied to `Set` instances |
|
|
189
|
+
| `byConstructor.URL` | `Transformer[]` | Applied to `URL` instances |
|
|
190
|
+
| `byConstructor.custom` | `CustomConstructorTransformerRegistration[]` | Custom types matched by `instanceof` in declaration order |
|
|
191
|
+
| `fallback` | `Transformer[]` | Applied when no earlier transformer returns a changed value |
|
|
192
|
+
|
|
193
|
+
The built-in transformers produce deterministic marker objects (e.g. `{ _transformer: 'date', datetime: '<ISO string>' }`). See [`docs/architecture/serialise-output.md`](docs/architecture/serialise-output.md) for the full dispatch order and marker shapes.
|
|
194
|
+
|
|
195
|
+
### `DiagnosticsOptions`
|
|
196
|
+
|
|
197
|
+
| Field | Type | Description |
|
|
198
|
+
|-------|------|-------------|
|
|
199
|
+
| `sink` | `(event: DiagnosticEvent) => void` | Called with a structured event when a value is degraded to `'[UNSUPPORTED]'` during serialisation |
|
|
200
|
+
|
|
201
|
+
## Public API
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
import { createRedactor, deepRedact } from '@hackylabs/deep-redact'
|
|
205
|
+
|
|
206
|
+
// deepRedact and createRedactor are the same factory under different names
|
|
207
|
+
const redact = deepRedact({ keys: ['secret'] })
|
|
208
|
+
const same = createRedactor({ keys: ['secret'] })
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Documentation
|
|
212
|
+
|
|
213
|
+
### Worked examples
|
|
214
|
+
|
|
215
|
+
- [Key targeting](docs/examples/key-targeting.md)
|
|
216
|
+
- [Fuzzy and case-insensitive key matching](docs/examples/fuzzy-key-matching.md)
|
|
217
|
+
- [Path targeting](docs/examples/path-targeting.md)
|
|
218
|
+
- [Wildcard path segments and ignore selectors](docs/examples/path-segment-ignore.md)
|
|
219
|
+
- [Regex property matching](docs/examples/regex-property-matching.md)
|
|
220
|
+
- [Per-key rule overrides](docs/examples/per-key-rule-overrides-string.md)
|
|
221
|
+
- [Substring targeting](docs/examples/substring-targeting.md)
|
|
222
|
+
- [Replacement and removal](docs/examples/replacement-and-removal.md)
|
|
223
|
+
- [Structured vs serialised output](docs/examples/serialised-output.md)
|
|
224
|
+
- [Custom transformers](docs/examples/custom-transformer.md)
|
|
225
|
+
- [Value-type allowlist](docs/examples/value-type-allowlist-default.md)
|
|
226
|
+
- [Singleton / service-root setup](docs/examples/singleton-setup.md)
|
|
227
|
+
- [Console redaction adapter](docs/examples/console-redaction.md)
|
|
228
|
+
|
|
229
|
+
### Migration guides
|
|
230
|
+
|
|
231
|
+
- [Migrating from Deep Redact v3](docs/migration/from-v3.md)
|
|
232
|
+
- [Migrating from fast-redact](docs/migration/from-fast-redact.md)
|
|
233
|
+
|
|
234
|
+
### Architecture and design
|
|
235
|
+
|
|
236
|
+
- [Rule precedence and evaluation order](docs/architecture/precedence.md)
|
|
237
|
+
- [Serialised output: transformer dispatch and marker shapes](docs/architecture/serialise-output.md)
|
|
238
|
+
- [One-way redaction contract](docs/architecture/one-way-redaction.md)
|
|
239
|
+
|
|
240
|
+
### Platform and security teams
|
|
241
|
+
|
|
242
|
+
- [Standardisation guide](docs/platform/standardisation-guide.md) — supported capabilities, targeting semantics, verification evidence, and adoption decision scope
|
|
243
|
+
|
|
244
|
+
## Scripts
|
|
245
|
+
|
|
246
|
+
- `pnpm run build`
|
|
247
|
+
- `pnpm run lint`
|
|
248
|
+
- `pnpm run test`
|
|
249
|
+
- `pnpm run generate-exports`
|
|
250
|
+
- `pnpm run generate-readme`
|
|
251
|
+
- `pnpm run verify-generated-files`
|
|
252
|
+
- `pnpm run bench`
|
|
253
|
+
- `pnpm run bench:generate-charts`
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_node_console_sink = require("../../node-console-sink-BnRUkAAr.cjs");
|
|
3
|
+
//#region src/core/diagnostics/console-diagnostic-event.ts
|
|
4
|
+
const consoleRecursionBlockedMessage = "Nested console adapter call was blocked to prevent a recursive redaction loop.";
|
|
5
|
+
const createConsoleRecursionBlockedDiagnosticEvent = (method) => {
|
|
6
|
+
return Object.freeze({
|
|
7
|
+
details: Object.freeze({ method }),
|
|
8
|
+
event: "console.recursion_blocked",
|
|
9
|
+
message: consoleRecursionBlockedMessage,
|
|
10
|
+
path: "",
|
|
11
|
+
valueType: "console"
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
const emitConsoleRecursionBlockedDiagnostic = (method, sink) => {
|
|
15
|
+
const resolvedSink = sink ?? require_node_console_sink.getNodeConsoleDiagnosticSink();
|
|
16
|
+
if (resolvedSink === void 0) return;
|
|
17
|
+
try {
|
|
18
|
+
resolvedSink(createConsoleRecursionBlockedDiagnosticEvent(method));
|
|
19
|
+
} catch {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/adapters/console/recursion-guard.ts
|
|
25
|
+
const createConsoleRecursionGuard = (options = {}) => {
|
|
26
|
+
let active = false;
|
|
27
|
+
let emittedForActiveChain = false;
|
|
28
|
+
const sink = options.diagnostics?.sink;
|
|
29
|
+
return {
|
|
30
|
+
block(method) {
|
|
31
|
+
if (!emittedForActiveChain) {
|
|
32
|
+
emittedForActiveChain = true;
|
|
33
|
+
emitConsoleRecursionBlockedDiagnostic(method, sink);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
isActive() {
|
|
37
|
+
return active;
|
|
38
|
+
},
|
|
39
|
+
run(operation) {
|
|
40
|
+
active = true;
|
|
41
|
+
emittedForActiveChain = false;
|
|
42
|
+
try {
|
|
43
|
+
return operation();
|
|
44
|
+
} finally {
|
|
45
|
+
active = false;
|
|
46
|
+
emittedForActiveChain = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/adapters/console/create-redacted-console.ts
|
|
53
|
+
const consoleMethodNames = Object.freeze([
|
|
54
|
+
"debug",
|
|
55
|
+
"error",
|
|
56
|
+
"info",
|
|
57
|
+
"log",
|
|
58
|
+
"trace",
|
|
59
|
+
"warn"
|
|
60
|
+
]);
|
|
61
|
+
const createRedactedConsole = (redactor, target, options = {}) => {
|
|
62
|
+
const guard = createConsoleRecursionGuard(options);
|
|
63
|
+
const adapted = {};
|
|
64
|
+
for (const method of consoleMethodNames) adapted[method] = (...args) => {
|
|
65
|
+
if (guard.isActive()) return guard.block(method);
|
|
66
|
+
return guard.run(() => {
|
|
67
|
+
const redactedArgs = args.map((argument) => redactor(argument));
|
|
68
|
+
return target[method](...redactedArgs);
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
return adapted;
|
|
72
|
+
};
|
|
73
|
+
//#endregion
|
|
74
|
+
exports.createRedactedConsole = createRedactedConsole;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { _ as DiagnosticSink, t as Redactor } from "../../public-Da0aARA9.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/console/create-redacted-console.d.ts
|
|
4
|
+
type ConsoleMethodName = 'debug' | 'error' | 'info' | 'log' | 'trace' | 'warn';
|
|
5
|
+
type ConsoleMethod = (...args: unknown[]) => unknown;
|
|
6
|
+
interface ConsoleLike {
|
|
7
|
+
readonly debug: ConsoleMethod;
|
|
8
|
+
readonly error: ConsoleMethod;
|
|
9
|
+
readonly info: ConsoleMethod;
|
|
10
|
+
readonly log: ConsoleMethod;
|
|
11
|
+
readonly trace: ConsoleMethod;
|
|
12
|
+
readonly warn: ConsoleMethod;
|
|
13
|
+
}
|
|
14
|
+
interface ConsoleRedactionOptions {
|
|
15
|
+
readonly diagnostics?: {
|
|
16
|
+
readonly sink?: DiagnosticSink;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
type RedactedConsole = ConsoleLike;
|
|
20
|
+
declare const createRedactedConsole: (redactor: Redactor, target: ConsoleLike, options?: ConsoleRedactionOptions) => RedactedConsole;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { type ConsoleLike, type ConsoleMethodName, type ConsoleRedactionOptions, type RedactedConsole, createRedactedConsole };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { _ as DiagnosticSink, t as Redactor } from "../../public-Dw4ycNzO.js";
|
|
2
|
+
|
|
3
|
+
//#region src/adapters/console/create-redacted-console.d.ts
|
|
4
|
+
type ConsoleMethodName = 'debug' | 'error' | 'info' | 'log' | 'trace' | 'warn';
|
|
5
|
+
type ConsoleMethod = (...args: unknown[]) => unknown;
|
|
6
|
+
interface ConsoleLike {
|
|
7
|
+
readonly debug: ConsoleMethod;
|
|
8
|
+
readonly error: ConsoleMethod;
|
|
9
|
+
readonly info: ConsoleMethod;
|
|
10
|
+
readonly log: ConsoleMethod;
|
|
11
|
+
readonly trace: ConsoleMethod;
|
|
12
|
+
readonly warn: ConsoleMethod;
|
|
13
|
+
}
|
|
14
|
+
interface ConsoleRedactionOptions {
|
|
15
|
+
readonly diagnostics?: {
|
|
16
|
+
readonly sink?: DiagnosticSink;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
type RedactedConsole = ConsoleLike;
|
|
20
|
+
declare const createRedactedConsole: (redactor: Redactor, target: ConsoleLike, options?: ConsoleRedactionOptions) => RedactedConsole;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { type ConsoleLike, type ConsoleMethodName, type ConsoleRedactionOptions, type RedactedConsole, createRedactedConsole };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { t as getNodeConsoleDiagnosticSink } from "../../node-console-sink-DaQleNZ8.js";
|
|
2
|
+
//#region src/core/diagnostics/console-diagnostic-event.ts
|
|
3
|
+
const consoleRecursionBlockedMessage = "Nested console adapter call was blocked to prevent a recursive redaction loop.";
|
|
4
|
+
const createConsoleRecursionBlockedDiagnosticEvent = (method) => {
|
|
5
|
+
return Object.freeze({
|
|
6
|
+
details: Object.freeze({ method }),
|
|
7
|
+
event: "console.recursion_blocked",
|
|
8
|
+
message: consoleRecursionBlockedMessage,
|
|
9
|
+
path: "",
|
|
10
|
+
valueType: "console"
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
const emitConsoleRecursionBlockedDiagnostic = (method, sink) => {
|
|
14
|
+
const resolvedSink = sink ?? getNodeConsoleDiagnosticSink();
|
|
15
|
+
if (resolvedSink === void 0) return;
|
|
16
|
+
try {
|
|
17
|
+
resolvedSink(createConsoleRecursionBlockedDiagnosticEvent(method));
|
|
18
|
+
} catch {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/adapters/console/recursion-guard.ts
|
|
24
|
+
const createConsoleRecursionGuard = (options = {}) => {
|
|
25
|
+
let active = false;
|
|
26
|
+
let emittedForActiveChain = false;
|
|
27
|
+
const sink = options.diagnostics?.sink;
|
|
28
|
+
return {
|
|
29
|
+
block(method) {
|
|
30
|
+
if (!emittedForActiveChain) {
|
|
31
|
+
emittedForActiveChain = true;
|
|
32
|
+
emitConsoleRecursionBlockedDiagnostic(method, sink);
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
isActive() {
|
|
36
|
+
return active;
|
|
37
|
+
},
|
|
38
|
+
run(operation) {
|
|
39
|
+
active = true;
|
|
40
|
+
emittedForActiveChain = false;
|
|
41
|
+
try {
|
|
42
|
+
return operation();
|
|
43
|
+
} finally {
|
|
44
|
+
active = false;
|
|
45
|
+
emittedForActiveChain = false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/adapters/console/create-redacted-console.ts
|
|
52
|
+
const consoleMethodNames = Object.freeze([
|
|
53
|
+
"debug",
|
|
54
|
+
"error",
|
|
55
|
+
"info",
|
|
56
|
+
"log",
|
|
57
|
+
"trace",
|
|
58
|
+
"warn"
|
|
59
|
+
]);
|
|
60
|
+
const createRedactedConsole = (redactor, target, options = {}) => {
|
|
61
|
+
const guard = createConsoleRecursionGuard(options);
|
|
62
|
+
const adapted = {};
|
|
63
|
+
for (const method of consoleMethodNames) adapted[method] = (...args) => {
|
|
64
|
+
if (guard.isActive()) return guard.block(method);
|
|
65
|
+
return guard.run(() => {
|
|
66
|
+
const redactedArgs = args.map((argument) => redactor(argument));
|
|
67
|
+
return target[method](...redactedArgs);
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
return adapted;
|
|
71
|
+
};
|
|
72
|
+
//#endregion
|
|
73
|
+
export { createRedactedConsole };
|