@keynv/redactor 0.1.0-rc.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 keynv-labs and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,10 @@
1
+ import type { RedactOptions, RedactResult } from './types.js';
2
+ /**
3
+ * Walks `patterns` and `text`, returning every non-overlapping hit.
4
+ *
5
+ * When two patterns overlap, the earlier-starting (longer when tied)
6
+ * match wins. This avoids double-redaction artifacts like
7
+ * `<REDACTED:postgres-uri>` further nibbled by `<REDACTED:high-entropy>`.
8
+ */
9
+ export declare function redact(text: string, opts?: RedactOptions): RedactResult;
10
+ //# sourceMappingURL=batch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAkB,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAkB9E;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,aAAkB,GAAG,YAAY,CA0F3E"}
package/dist/batch.js ADDED
@@ -0,0 +1,111 @@
1
+ import { findEntropyMatches } from './entropy.js';
2
+ import { BUILTIN_PATTERNS } from './patterns.js';
3
+ const ENTROPY_PATTERN_NAME = 'high-entropy';
4
+ const LITERAL_PATTERN_NAME = 'literal-alias-resolved-value';
5
+ function defaultRender(name) {
6
+ return `<REDACTED:${name}>`;
7
+ }
8
+ function preview(matched) {
9
+ if (matched.length <= 4)
10
+ return '****';
11
+ return `${matched.slice(0, 3)}...`;
12
+ }
13
+ function escapeRegExp(s) {
14
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
15
+ }
16
+ /**
17
+ * Walks `patterns` and `text`, returning every non-overlapping hit.
18
+ *
19
+ * When two patterns overlap, the earlier-starting (longer when tied)
20
+ * match wins. This avoids double-redaction artifacts like
21
+ * `<REDACTED:postgres-uri>` further nibbled by `<REDACTED:high-entropy>`.
22
+ */
23
+ export function redact(text, opts = {}) {
24
+ if (typeof text !== 'string' || text.length === 0) {
25
+ return { text, matches: [] };
26
+ }
27
+ const patterns = [
28
+ ...(opts.patterns ?? BUILTIN_PATTERNS),
29
+ ...(opts.extraPatterns ?? []),
30
+ ];
31
+ // Pattern matches.
32
+ const raw = [];
33
+ for (const pattern of patterns) {
34
+ pattern.regex.lastIndex = 0;
35
+ for (const m of text.matchAll(pattern.regex)) {
36
+ if (m.index === undefined)
37
+ continue;
38
+ raw.push({
39
+ pattern: pattern.name,
40
+ start: m.index,
41
+ end: m.index + m[0].length,
42
+ preview: preview(m[0]),
43
+ });
44
+ }
45
+ }
46
+ // Literal matches (e.g., resolved alias values that the caller wants
47
+ // pre-emptively redacted).
48
+ if (opts.literals && opts.literals.length > 0) {
49
+ for (const literal of opts.literals) {
50
+ if (!literal)
51
+ continue;
52
+ const re = new RegExp(escapeRegExp(literal), 'g');
53
+ for (const m of text.matchAll(re)) {
54
+ if (m.index === undefined)
55
+ continue;
56
+ raw.push({
57
+ pattern: LITERAL_PATTERN_NAME,
58
+ start: m.index,
59
+ end: m.index + literal.length,
60
+ preview: preview(literal),
61
+ });
62
+ }
63
+ }
64
+ }
65
+ // Entropy detector.
66
+ const entropyMatches = findEntropyMatches(text, opts.entropy);
67
+ for (const m of entropyMatches) {
68
+ raw.push({
69
+ pattern: ENTROPY_PATTERN_NAME,
70
+ start: m.start,
71
+ end: m.end,
72
+ preview: preview(m.token),
73
+ });
74
+ }
75
+ if (raw.length === 0)
76
+ return { text, matches: [] };
77
+ // Sort and de-overlap (earliest start wins; longer match wins on tie).
78
+ raw.sort((a, b) => a.start - b.start || b.end - b.start - (a.end - a.start));
79
+ const merged = [];
80
+ for (const m of raw) {
81
+ const last = merged[merged.length - 1];
82
+ if (!last || m.start >= last.end) {
83
+ merged.push({ ...m });
84
+ }
85
+ else if (m.end > last.end) {
86
+ // Partial overlap: extend the surviving match to cover the union span.
87
+ // Dropping `m` outright would leave characters (last.end, m.end) — the
88
+ // tail of a real secret — un-redacted. Extending only ever redacts
89
+ // MORE, never less, so it is strictly safer.
90
+ last.end = m.end;
91
+ }
92
+ }
93
+ // Build the redacted output in a single left-to-right pass. `merged` is
94
+ // sorted ascending and non-overlapping, so we can push [gap, token] pieces
95
+ // and join once — O(n) instead of the O(matches × filesize) rebuild that a
96
+ // per-match slice+concat incurs on large scans.
97
+ const pieces = [];
98
+ let cursor = 0;
99
+ for (const m of merged) {
100
+ if (m.start > cursor)
101
+ pieces.push(text.slice(cursor, m.start));
102
+ const original = text.slice(m.start, m.end);
103
+ const renderer = patterns.find((p) => p.name === m.pattern)?.redactWith ?? (() => defaultRender(m.pattern));
104
+ pieces.push(renderer(original));
105
+ cursor = m.end;
106
+ }
107
+ if (cursor < text.length)
108
+ pieces.push(text.slice(cursor));
109
+ return { text: pieces.join(''), matches: merged };
110
+ }
111
+ //# sourceMappingURL=batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"batch.js","sourceRoot":"","sources":["../src/batch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { EntropyOptions } from './types.js';
2
+ /**
3
+ * Shannon entropy of a string in bits-per-character. Higher = more
4
+ * "random-looking". Real secrets typically score 4.0-6.0 bits/char.
5
+ */
6
+ export declare function shannonEntropy(s: string): number;
7
+ export interface EntropyMatch {
8
+ start: number;
9
+ end: number;
10
+ token: string;
11
+ }
12
+ /**
13
+ * Finds high-entropy tokens within `text`. Tokens are split on
14
+ * whitespace + common separators; each candidate token is checked
15
+ * against the length and entropy thresholds.
16
+ */
17
+ export declare function findEntropyMatches(text: string, opts?: EntropyOptions): EntropyMatch[];
18
+ //# sourceMappingURL=entropy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entropy.d.ts","sourceRoot":"","sources":["../src/entropy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAqBjD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAWhD;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,cAAmB,GAAG,YAAY,EAAE,CA4B1F"}
@@ -0,0 +1,70 @@
1
+ /** Token boundary characters. We split on whitespace, quotes, and common
2
+ * separators that appear around credential-shaped substrings. */
3
+ const TOKEN_BOUNDARY_RE = /[\s,;:'"<>(){}[\]=]+/;
4
+ const DEFAULT_EXCLUDE_PREFIXES = [
5
+ 'sha1-',
6
+ 'sha256:',
7
+ 'sha256-',
8
+ 'sha384-',
9
+ 'sha512-',
10
+ ];
11
+ const DEFAULTS = {
12
+ enabled: true,
13
+ minLength: 24,
14
+ minBitsPerChar: 4.5,
15
+ excludePrefixes: DEFAULT_EXCLUDE_PREFIXES,
16
+ };
17
+ /**
18
+ * Shannon entropy of a string in bits-per-character. Higher = more
19
+ * "random-looking". Real secrets typically score 4.0-6.0 bits/char.
20
+ */
21
+ export function shannonEntropy(s) {
22
+ if (s.length === 0)
23
+ return 0;
24
+ const counts = new Map();
25
+ for (const ch of s)
26
+ counts.set(ch, (counts.get(ch) ?? 0) + 1);
27
+ const n = s.length;
28
+ let h = 0;
29
+ for (const c of counts.values()) {
30
+ const p = c / n;
31
+ h -= p * Math.log2(p);
32
+ }
33
+ return h;
34
+ }
35
+ /**
36
+ * Finds high-entropy tokens within `text`. Tokens are split on
37
+ * whitespace + common separators; each candidate token is checked
38
+ * against the length and entropy thresholds.
39
+ */
40
+ export function findEntropyMatches(text, opts = {}) {
41
+ const cfg = { ...DEFAULTS, ...opts };
42
+ if (!cfg.enabled)
43
+ return [];
44
+ const matches = [];
45
+ let i = 0;
46
+ while (i < text.length) {
47
+ // Skip token boundaries.
48
+ while (i < text.length && TOKEN_BOUNDARY_RE.test(text[i] ?? ''))
49
+ i++;
50
+ if (i >= text.length)
51
+ break;
52
+ const start = i;
53
+ while (i < text.length && !TOKEN_BOUNDARY_RE.test(text[i] ?? ''))
54
+ i++;
55
+ const end = i;
56
+ const token = text.slice(start, end);
57
+ if (token.length >= cfg.minLength) {
58
+ const lower = token.toLowerCase();
59
+ const excluded = cfg.excludePrefixes.some((p) => lower.startsWith(p));
60
+ if (!excluded) {
61
+ const h = shannonEntropy(token);
62
+ if (h >= cfg.minBitsPerChar) {
63
+ matches.push({ start, end, token });
64
+ }
65
+ }
66
+ }
67
+ }
68
+ return matches;
69
+ }
70
+ //# sourceMappingURL=entropy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entropy.js","sourceRoot":"","sources":["../src/entropy.ts"],"names":[],"mappings":"AAEA;iEACiE;AACjE,MAAM,iBAAiB,GAAG,sBAAsB,CAAC;AAEjD,MAAM,wBAAwB,GAA0B;IACtD,OAAO;IACP,SAAS;IACT,SAAS;IACT,SAAS;IACT,SAAS;CACV,CAAC;AAEF,MAAM,QAAQ,GAA6B;IACzC,OAAO,EAAE,IAAI;IACb,SAAS,EAAE,EAAE;IACb,cAAc,EAAE,GAAG;IACnB,eAAe,EAAE,wBAAwB;CAC1C,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,EAAE,IAAI,CAAC;QAAE,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9D,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IACnB,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAQD;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,OAAuB,EAAE;IACxE,MAAM,GAAG,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,IAAI,EAAE,CAAC;IACrC,IAAI,CAAC,GAAG,CAAC,OAAO;QAAE,OAAO,EAAE,CAAC;IAE5B,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,yBAAyB;QACzB,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAAE,CAAC,EAAE,CAAC;QACrE,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM;YAAE,MAAM;QAE5B,MAAM,KAAK,GAAG,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAAE,CAAC,EAAE,CAAC;QACtE,MAAM,GAAG,GAAG,CAAC,CAAC;QACd,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAErC,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAClC,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;YAClC,MAAM,QAAQ,GAAG,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;YACtE,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,MAAM,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;gBAChC,IAAI,CAAC,IAAI,GAAG,CAAC,cAAc,EAAE,CAAC;oBAC5B,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;gBACtC,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export type { EntropyOptions, Match, Pattern, RedactionSummary, RedactOptions, RedactResult, } from './types.js';
2
+ export { BUILTIN_PATTERNS, BUILTIN_LINE_PATTERNS } from './patterns.js';
3
+ export { redact } from './batch.js';
4
+ export { createRedactStream, type StreamingOptions } from './streaming.js';
5
+ export { findEntropyMatches, shannonEntropy } from './entropy.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,cAAc,EACd,KAAK,EACL,OAAO,EACP,gBAAgB,EAChB,aAAa,EACb,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { BUILTIN_PATTERNS, BUILTIN_LINE_PATTERNS } from './patterns.js';
2
+ export { redact } from './batch.js';
3
+ export { createRedactStream } from './streaming.js';
4
+ export { findEntropyMatches, shannonEntropy } from './entropy.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAyB,MAAM,gBAAgB,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,16 @@
1
+ import type { Pattern } from './types.js';
2
+ /**
3
+ * Built-in pattern bank. Mirrored 1:1 with docs/02-threat-model.md
4
+ * §pattern-bank. Adding a pattern:
5
+ * 1. Append the entry here.
6
+ * 2. Add a regression test in patterns.test.ts (positive + a negative
7
+ * to verify it doesn't false-positive on innocuous fixtures).
8
+ * 3. Update docs/02-threat-model.md.
9
+ */
10
+ export declare const BUILTIN_PATTERNS: ReadonlyArray<Pattern>;
11
+ /**
12
+ * The subset of `BUILTIN_PATTERNS` that are safe to apply per-line in
13
+ * a streaming context (no multiline regexes).
14
+ */
15
+ export declare const BUILTIN_LINE_PATTERNS: ReadonlyArray<Pattern>;
16
+ //# sourceMappingURL=patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../src/patterns.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,EAAE,aAAa,CAAC,OAAO,CA8HnD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC,OAAO,CAExD,CAAC"}
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Built-in pattern bank. Mirrored 1:1 with docs/02-threat-model.md
3
+ * §pattern-bank. Adding a pattern:
4
+ * 1. Append the entry here.
5
+ * 2. Add a regression test in patterns.test.ts (positive + a negative
6
+ * to verify it doesn't false-positive on innocuous fixtures).
7
+ * 3. Update docs/02-threat-model.md.
8
+ */
9
+ export const BUILTIN_PATTERNS = [
10
+ // URI-shaped credentials
11
+ {
12
+ name: 'postgres-uri',
13
+ regex: /postgres(?:ql)?:\/\/[^\s'"<>]+/g,
14
+ },
15
+ {
16
+ name: 'mysql-uri',
17
+ regex: /mysql:\/\/[^\s'"<>]+/g,
18
+ },
19
+ {
20
+ name: 'mongodb-uri',
21
+ regex: /mongodb(?:\+srv)?:\/\/[^\s'"<>]+/g,
22
+ },
23
+ {
24
+ name: 'redis-uri-with-password',
25
+ regex: /rediss?:\/\/[^@\s'"<>]+@[^\s'"<>]+/g,
26
+ },
27
+ {
28
+ name: 'slack-webhook',
29
+ regex: /https:\/\/hooks\.slack\.com\/services\/[A-Z0-9]+\/[A-Z0-9]+\/[A-Za-z0-9]+/g,
30
+ },
31
+ // Cloud provider keys
32
+ {
33
+ name: 'aws-access-key-id',
34
+ regex: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g,
35
+ },
36
+ {
37
+ name: 'gcp-api-key',
38
+ regex: /\bAIza[0-9A-Za-z_-]{35}\b/g,
39
+ },
40
+ // Vendor-prefixed tokens
41
+ {
42
+ name: 'github-pat-classic',
43
+ regex: /\bghp_[A-Za-z0-9]{36}\b/g,
44
+ },
45
+ {
46
+ name: 'github-oauth-user-to-server',
47
+ regex: /\bghu_[A-Za-z0-9]{36}\b/g,
48
+ },
49
+ {
50
+ name: 'github-oauth-server-to-server',
51
+ regex: /\bgho_[A-Za-z0-9]{36}\b/g,
52
+ },
53
+ {
54
+ // Fine-grained PATs are commonly 82 chars after the prefix but we
55
+ // accept >=40 to cover dev-mode and future variations.
56
+ name: 'github-pat-fine-grained',
57
+ regex: /\bgithub_pat_[A-Za-z0-9_]{40,}\b/g,
58
+ },
59
+ {
60
+ name: 'slack-bot-token',
61
+ regex: /\bxoxb-[A-Za-z0-9-]{24,}\b/g,
62
+ },
63
+ {
64
+ name: 'slack-user-token',
65
+ regex: /\bxoxp-[A-Za-z0-9-]{24,}\b/g,
66
+ },
67
+ {
68
+ name: 'stripe-live-secret-key',
69
+ regex: /\bsk_live_[A-Za-z0-9]{24,}\b/g,
70
+ },
71
+ {
72
+ name: 'stripe-test-secret-key',
73
+ regex: /\bsk_test_[A-Za-z0-9]{24,}\b/g,
74
+ },
75
+ {
76
+ name: 'stripe-restricted-live-key',
77
+ regex: /\brk_live_[A-Za-z0-9]{24,}\b/g,
78
+ },
79
+ {
80
+ name: 'stripe-restricted-test-key',
81
+ regex: /\brk_test_[A-Za-z0-9]{24,}\b/g,
82
+ },
83
+ {
84
+ // Negative lookahead skips Anthropic-prefixed keys so the more
85
+ // specific anthropic-api-key pattern wins on those.
86
+ name: 'openai-api-key',
87
+ regex: /\bsk-(?!ant-)(?:proj-)?[A-Za-z0-9_-]{20,}\b/g,
88
+ },
89
+ {
90
+ name: 'anthropic-api-key',
91
+ regex: /\bsk-ant-(?:api03|admin01)-[A-Za-z0-9_-]{20,}\b/g,
92
+ },
93
+ // Hex-shaped vendor tokens. The entropy backstop structurally cannot
94
+ // catch hex (max Shannon entropy log2(16)=4.0 < the 4.5 threshold), so
95
+ // these are matched by their distinctive prefixes — high precision, near
96
+ // zero false positives, unlike a blanket "long hex" rule which would fire
97
+ // on git SHAs and content hashes.
98
+ {
99
+ name: 'twilio-account-sid',
100
+ regex: /\bAC[0-9a-f]{32}\b/g,
101
+ },
102
+ {
103
+ name: 'twilio-api-key-sid',
104
+ regex: /\bSK[0-9a-f]{32}\b/g,
105
+ },
106
+ {
107
+ name: 'mailgun-api-key',
108
+ regex: /\bkey-[0-9a-f]{32}\b/g,
109
+ },
110
+ {
111
+ name: 'sendgrid-api-key',
112
+ regex: /\bSG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}\b/g,
113
+ },
114
+ // Structured tokens
115
+ {
116
+ name: 'jwt',
117
+ regex: /\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g,
118
+ },
119
+ // PEM-armored private keys (multiline; not applied in streaming mode)
120
+ {
121
+ name: 'pem-private-key',
122
+ regex: /-----BEGIN [A-Z][A-Z ]*PRIVATE KEY-----[\s\S]+?-----END [A-Z][A-Z ]*PRIVATE KEY-----/g,
123
+ multiline: true,
124
+ },
125
+ {
126
+ name: 'pgp-private-key',
127
+ regex: /-----BEGIN PGP PRIVATE KEY BLOCK-----[\s\S]+?-----END PGP PRIVATE KEY BLOCK-----/g,
128
+ multiline: true,
129
+ },
130
+ ];
131
+ /**
132
+ * The subset of `BUILTIN_PATTERNS` that are safe to apply per-line in
133
+ * a streaming context (no multiline regexes).
134
+ */
135
+ export const BUILTIN_LINE_PATTERNS = BUILTIN_PATTERNS.filter((p) => !p.multiline);
136
+ //# sourceMappingURL=patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../src/patterns.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAA2B;IACtD,yBAAyB;IACzB;QACE,IAAI,EAAE,cAAc;QACpB,KAAK,EAAE,iCAAiC;KACzC;IACD;QACE,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,uBAAuB;KAC/B;IACD;QACE,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,mCAAmC;KAC3C;IACD;QACE,IAAI,EAAE,yBAAyB;QAC/B,KAAK,EAAE,qCAAqC;KAC7C;IACD;QACE,IAAI,EAAE,eAAe;QACrB,KAAK,EAAE,4EAA4E;KACpF;IAED,sBAAsB;IACtB;QACE,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE,gCAAgC;KACxC;IACD;QACE,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,4BAA4B;KACpC;IAED,yBAAyB;IACzB;QACE,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,0BAA0B;KAClC;IACD;QACE,IAAI,EAAE,6BAA6B;QACnC,KAAK,EAAE,0BAA0B;KAClC;IACD;QACE,IAAI,EAAE,+BAA+B;QACrC,KAAK,EAAE,0BAA0B;KAClC;IACD;QACE,kEAAkE;QAClE,uDAAuD;QACvD,IAAI,EAAE,yBAAyB;QAC/B,KAAK,EAAE,mCAAmC;KAC3C;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,KAAK,EAAE,6BAA6B;KACrC;IACD;QACE,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE,6BAA6B;KACrC;IACD;QACE,IAAI,EAAE,wBAAwB;QAC9B,KAAK,EAAE,+BAA+B;KACvC;IACD;QACE,IAAI,EAAE,wBAAwB;QAC9B,KAAK,EAAE,+BAA+B;KACvC;IACD;QACE,IAAI,EAAE,4BAA4B;QAClC,KAAK,EAAE,+BAA+B;KACvC;IACD;QACE,IAAI,EAAE,4BAA4B;QAClC,KAAK,EAAE,+BAA+B;KACvC;IACD;QACE,+DAA+D;QAC/D,oDAAoD;QACpD,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,8CAA8C;KACtD;IACD;QACE,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE,kDAAkD;KAC1D;IAED,qEAAqE;IACrE,uEAAuE;IACvE,yEAAyE;IACzE,0EAA0E;IAC1E,kCAAkC;IAClC;QACE,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,qBAAqB;KAC7B;IACD;QACE,IAAI,EAAE,oBAAoB;QAC1B,KAAK,EAAE,qBAAqB;KAC7B;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,KAAK,EAAE,uBAAuB;KAC/B;IACD;QACE,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE,+CAA+C;KACvD;IAED,oBAAoB;IACpB;QACE,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,uEAAuE;KAC/E;IAED,sEAAsE;IACtE;QACE,IAAI,EAAE,iBAAiB;QACvB,KAAK,EAAE,uFAAuF;QAC9F,SAAS,EAAE,IAAI;KAChB;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,KAAK,EAAE,mFAAmF;QAC1F,SAAS,EAAE,IAAI;KAChB;CACF,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAA2B,gBAAgB,CAAC,MAAM,CAClF,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CACpB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=patterns.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.test.d.ts","sourceRoot":"","sources":["../src/patterns.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,193 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { redact } from './batch.js';
3
+ // Vendor-prefixed fixtures are constructed at runtime from
4
+ // concatenated pieces so the LITERAL secret-shape never appears
5
+ // verbatim in source. Static scanners (GitHub Push Protection,
6
+ // trufflehog, gitleaks) match against contiguous source bytes;
7
+ // concatenation keeps our regex tests honest while preventing
8
+ // false positives at commit time.
9
+ const X = (n) => 'X'.repeat(n);
10
+ const fix = {
11
+ awsKey: `AKIA${'EXAMPLE'.repeat(2)}EX`,
12
+ awsTemp: `ASIA${'EXAMPLE'.repeat(2)}DD`,
13
+ gcp: `${'AIza'}${X(35)}`,
14
+ ghClassic: `${'ghp'}_${X(36)}`,
15
+ ghFine: `${'github'}_pat_${X(60)}`,
16
+ slackBot: `${'xoxb'}-${X(10)}-${X(13)}-${X(24)}`,
17
+ slackHook: `https://hooks.slack.com/services/${X(10).replace(/X/g, 'T')}/${X(10).replace(/X/g, 'B')}/${X(26)}`,
18
+ stripe: `${'sk'}_${'live'}_${X(26)}`,
19
+ openai: `${'sk'}-proj-${X(24)}`,
20
+ anthropic: `${'sk'}-ant-api03-${X(24)}`,
21
+ jwt: `${'eyJ'}${X(20)}.${'eyJ'}${X(20)}.${X(20)}`,
22
+ twilioSid: `${'AC'}${'a1b2c3d4'.repeat(4)}`,
23
+ twilioKey: `${'SK'}${'a1b2c3d4'.repeat(4)}`,
24
+ mailgun: `${'key'}-${'a1b2c3d4'.repeat(4)}`,
25
+ sendgrid: `${'SG'}.${X(22)}.${X(43)}`,
26
+ };
27
+ describe('pattern bank — true positives', () => {
28
+ it.each([
29
+ [
30
+ 'postgres URI',
31
+ 'connect to postgres://app:hunter2@db.example.com:5432/billing for the migration',
32
+ 'postgres-uri',
33
+ ],
34
+ ['mysql URI', 'mysql://root:rootpass@10.0.0.5:3306/app', 'mysql-uri'],
35
+ ['mongodb+srv URI', 'mongodb+srv://u:p@cluster0.mongo.net/app?retryWrites=true', 'mongodb-uri'],
36
+ [
37
+ 'Redis with password',
38
+ 'rediss://default:abc123@redis.example.com:6379/0',
39
+ 'redis-uri-with-password',
40
+ ],
41
+ ['Slack webhook', `POST ${fix.slackHook}`, 'slack-webhook'],
42
+ ['AWS AKIA', `AWS_ACCESS_KEY_ID=${fix.awsKey}`, 'aws-access-key-id'],
43
+ ['AWS ASIA temp', `token=${fix.awsTemp}`, 'aws-access-key-id'],
44
+ ['GCP API key', `key: ${fix.gcp}`, 'gcp-api-key'],
45
+ ['GitHub PAT classic', `gh auth login --token ${fix.ghClassic}`, 'github-pat-classic'],
46
+ ['GitHub fine-grained PAT', `PAT=${fix.ghFine}`, 'github-pat-fine-grained'],
47
+ ['Slack bot token', `export SLACK_BOT=${fix.slackBot}`, 'slack-bot-token'],
48
+ ['Stripe live key', `STRIPE_KEY=${fix.stripe}`, 'stripe-live-secret-key'],
49
+ ['OpenAI API key', `OPENAI_API_KEY=${fix.openai}`, 'openai-api-key'],
50
+ ['Anthropic API key', `ANTHROPIC_API_KEY=${fix.anthropic}`, 'anthropic-api-key'],
51
+ ['Twilio Account SID', `TWILIO_SID=${fix.twilioSid}`, 'twilio-account-sid'],
52
+ ['Twilio API Key SID', `TWILIO_KEY=${fix.twilioKey}`, 'twilio-api-key-sid'],
53
+ ['Mailgun API key', `MAILGUN_KEY=${fix.mailgun}`, 'mailgun-api-key'],
54
+ ['SendGrid API key', `SENDGRID_KEY=${fix.sendgrid}`, 'sendgrid-api-key'],
55
+ ['JWT', `Authorization: Bearer ${fix.jwt}`, 'jwt'],
56
+ ])('redacts %s', (_label, input, expectedPattern) => {
57
+ const { text, matches } = redact(input);
58
+ expect(matches.length).toBeGreaterThan(0);
59
+ expect(matches.some((m) => m.pattern === expectedPattern)).toBe(true);
60
+ expect(text).toContain(`<REDACTED:${expectedPattern}>`);
61
+ });
62
+ it('redacts multi-line PEM private key blocks', () => {
63
+ const input = [
64
+ 'this is a key:',
65
+ '-----BEGIN RSA PRIVATE KEY-----',
66
+ 'MIIEpAIBAAKCAQEA1234567890abcdef',
67
+ 'MIIEpAIBAAKCAQEAabcdef1234567890',
68
+ '-----END RSA PRIVATE KEY-----',
69
+ 'continuing',
70
+ ].join('\n');
71
+ const { text, matches } = redact(input);
72
+ expect(matches.some((m) => m.pattern === 'pem-private-key')).toBe(true);
73
+ expect(text).toContain('<REDACTED:pem-private-key>');
74
+ expect(text).not.toContain('MIIEpAIBAAKCAQEA');
75
+ });
76
+ it('redacts multi-line PGP private key blocks', () => {
77
+ const input = [
78
+ '-----BEGIN PGP PRIVATE KEY BLOCK-----',
79
+ 'Comment: gpg pretend key',
80
+ 'lQOYBGFbS7gBCAD...',
81
+ '-----END PGP PRIVATE KEY BLOCK-----',
82
+ ].join('\n');
83
+ const { text } = redact(input);
84
+ expect(text).toContain('<REDACTED:pgp-private-key>');
85
+ });
86
+ });
87
+ describe('pattern bank — false positives (innocent fixtures must NOT be redacted)', () => {
88
+ // To reduce flakiness, run the entropy detector with default settings —
89
+ // the patterns themselves should not match any of these.
90
+ const innocent = [
91
+ ['UUIDv4', '550e8400-e29b-41d4-a716-446655440000'],
92
+ ['short git SHA', 'a3f9b8c'],
93
+ ['long git SHA', 'a3f9b8c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7'],
94
+ // A bare 32-char hex (MD5-shaped) with no vendor prefix must NOT match —
95
+ // proves we did not add a blanket "long hex" rule that would fire on
96
+ // content hashes and git SHAs.
97
+ ['bare md5 hash (no vendor prefix)', 'd41d8cd98f00b204e9800998ecf8427e'],
98
+ ['lorem ipsum', 'lorem ipsum dolor sit amet consectetur adipiscing elit'],
99
+ ['file path', '/usr/local/bin/keynv-server'],
100
+ [
101
+ 'public RSA key marker (not private)',
102
+ '-----BEGIN PUBLIC KEY----- ... -----END PUBLIC KEY-----',
103
+ ],
104
+ ['email address', 'support@billing.example.com'],
105
+ ['public GitHub URL', 'https://github.com/anthropic/keynv'],
106
+ ['version string', 'keynv 1.2.3-rc.4'],
107
+ ];
108
+ // We disable the entropy detector here to isolate pattern-only behavior.
109
+ it.each(innocent)('does not pattern-match %s', (_label, input) => {
110
+ const { matches } = redact(input, { entropy: { enabled: false } });
111
+ expect(matches).toEqual([]);
112
+ });
113
+ it('does not entropy-flag npm package-lock.json sha512 integrity hashes', () => {
114
+ const hash = 'sha512-YFlf4sNAz0cAFGY0SGOC4V9T3zDIJUyCJmI6EMbqTX4UPIxw9VpBBPuRgL3Q3eD7F3m7ixFW7Lh6wj3fIlzg==';
115
+ const { matches } = redact(hash);
116
+ expect(matches).toEqual([]);
117
+ });
118
+ it('does not entropy-flag Docker image digests (sha256:hex)', () => {
119
+ const digest = 'sha256:a3f9b8c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a74f3b1c9d2e8a0f5b7c6d4e2f1';
120
+ const { matches } = redact(digest);
121
+ expect(matches).toEqual([]);
122
+ });
123
+ it('does not entropy-flag sha384 subresource integrity attributes', () => {
124
+ const sri = 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC';
125
+ const { matches } = redact(sri);
126
+ expect(matches).toEqual([]);
127
+ });
128
+ });
129
+ describe('redact — overlap and ordering', () => {
130
+ it('renders matches in order without index drift', () => {
131
+ const input = `a=${fix.ghClassic} b=${fix.awsKey}`;
132
+ const { text } = redact(input, { entropy: { enabled: false } });
133
+ expect(text).toBe('a=<REDACTED:github-pat-classic> b=<REDACTED:aws-access-key-id>');
134
+ });
135
+ it('does not double-redact overlapping pattern hits', () => {
136
+ // jwt + entropy could both match; the wider pattern (jwt) wins.
137
+ const jwt = fix.jwt;
138
+ const input = `token: ${jwt}`;
139
+ const { text, matches } = redact(input);
140
+ expect(text).toContain('<REDACTED:jwt>');
141
+ expect(matches.filter((m) => m.start < input.indexOf(jwt) + jwt.length).length).toBe(1);
142
+ });
143
+ it('does not leak the tail of a longer secret on partial overlap', () => {
144
+ // Two resolved values share overlapping text. The earlier, shorter match
145
+ // must NOT cause the tail of the later one to survive un-redacted: the
146
+ // de-overlap step extends to the union span rather than dropping the
147
+ // second hit. Regression for the "tail leak" (batch de-overlap loop).
148
+ const input = 'abc123def456ghi';
149
+ const { text } = redact(input, {
150
+ literals: ['abc123def', 'def456ghi'],
151
+ entropy: { enabled: false },
152
+ });
153
+ // Every character of either secret must be gone — no plaintext tail.
154
+ expect(text).not.toContain('abc123def');
155
+ expect(text).not.toContain('def456ghi');
156
+ expect(text).not.toContain('456ghi');
157
+ expect(text).not.toMatch(/[0-9]/);
158
+ });
159
+ });
160
+ describe('redact — literals (resolved-value pre-emptive redaction)', () => {
161
+ it('redacts an arbitrary literal exact-match', () => {
162
+ const { text, matches } = redact('the password is hunter2 and another hunter2', {
163
+ literals: ['hunter2'],
164
+ entropy: { enabled: false },
165
+ });
166
+ expect(text).not.toContain('hunter2');
167
+ expect(matches.length).toBe(2);
168
+ });
169
+ it('escapes regex metacharacters in literal', () => {
170
+ const { text } = redact('value: a.b+c?d', {
171
+ literals: ['a.b+c?d'],
172
+ entropy: { enabled: false },
173
+ });
174
+ expect(text).not.toContain('a.b+c?d');
175
+ });
176
+ });
177
+ describe('redact — entropy detector', () => {
178
+ it('catches high-entropy strings (random base64-shaped)', () => {
179
+ const { matches } = redact('payload: A8sJ19sbgZ7GqbkpRXp9-ZQyCPmK3VBh2');
180
+ expect(matches.some((m) => m.pattern === 'high-entropy')).toBe(true);
181
+ });
182
+ it('respects minLength threshold', () => {
183
+ const { matches } = redact('short: A8sJ19sbgZ', { entropy: { minLength: 24 } });
184
+ expect(matches).toEqual([]);
185
+ });
186
+ it('can be disabled', () => {
187
+ const { matches } = redact('payload: A8sJ19sbgZ7GqbkpRXp9-ZQyCPmK3VBh2', {
188
+ entropy: { enabled: false },
189
+ });
190
+ expect(matches).toEqual([]);
191
+ });
192
+ });
193
+ //# sourceMappingURL=patterns.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.test.js","sourceRoot":"","sources":["../src/patterns.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { Transform } from 'node:stream';
2
+ import type { RedactOptions } from './types.js';
3
+ export interface StreamingOptions extends RedactOptions {
4
+ /** Hard cap on per-line buffer. Default 64 KB. */
5
+ bufferLimit?: number;
6
+ }
7
+ /**
8
+ * Constructs a Transform stream that line-buffers input, runs each
9
+ * complete line through `redact`, and emits the redacted line.
10
+ *
11
+ * Multi-line patterns from the builtin bank (RSA / PGP private keys)
12
+ * are NOT applied here — by definition they span multiple lines, and
13
+ * detecting them across chunk boundaries requires unbounded buffering.
14
+ * The batch API handles those; for streaming, a private key spanning
15
+ * many lines will leak unless the caller adds a custom matcher with
16
+ * explicit windowing.
17
+ *
18
+ * Use case: piped between a privileged subprocess's stdout/stderr and
19
+ * the AI agent's tool-output channel.
20
+ */
21
+ export declare function createRedactStream(opts?: StreamingOptions): Transform;
22
+ //# sourceMappingURL=streaming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAA0B,MAAM,aAAa,CAAC;AAGhE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAUhD,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,GAAE,gBAAqB,GAAG,SAAS,CAwCzE"}
@@ -0,0 +1,65 @@
1
+ import { Transform } from 'node:stream';
2
+ import { redact } from './batch.js';
3
+ import { BUILTIN_LINE_PATTERNS } from './patterns.js';
4
+ /**
5
+ * Hard cap on the in-memory line buffer. Subprocess output containing
6
+ * an unterminated line (e.g., interactive prompts that don't print
7
+ * \n) is flushed once the buffer reaches this size to avoid unbounded
8
+ * memory growth. Default: 64 KB.
9
+ */
10
+ const DEFAULT_BUFFER_LIMIT = 64 * 1024;
11
+ /**
12
+ * Constructs a Transform stream that line-buffers input, runs each
13
+ * complete line through `redact`, and emits the redacted line.
14
+ *
15
+ * Multi-line patterns from the builtin bank (RSA / PGP private keys)
16
+ * are NOT applied here — by definition they span multiple lines, and
17
+ * detecting them across chunk boundaries requires unbounded buffering.
18
+ * The batch API handles those; for streaming, a private key spanning
19
+ * many lines will leak unless the caller adds a custom matcher with
20
+ * explicit windowing.
21
+ *
22
+ * Use case: piped between a privileged subprocess's stdout/stderr and
23
+ * the AI agent's tool-output channel.
24
+ */
25
+ export function createRedactStream(opts = {}) {
26
+ const lineOpts = {
27
+ patterns: opts.patterns ?? BUILTIN_LINE_PATTERNS,
28
+ ...(opts.extraPatterns ? { extraPatterns: opts.extraPatterns } : {}),
29
+ ...(opts.literals ? { literals: opts.literals } : {}),
30
+ ...(opts.entropy ? { entropy: opts.entropy } : {}),
31
+ };
32
+ const bufferLimit = opts.bufferLimit ?? DEFAULT_BUFFER_LIMIT;
33
+ let buf = '';
34
+ function flushBufferThroughRedactor() {
35
+ if (buf.length === 0)
36
+ return '';
37
+ const result = redact(buf, lineOpts);
38
+ buf = '';
39
+ return result.text;
40
+ }
41
+ return new Transform({
42
+ transform(chunk, _enc, cb) {
43
+ buf += chunk.toString('utf8');
44
+ let out = '';
45
+ let nl;
46
+ // biome-ignore lint/suspicious/noAssignInExpressions: idiomatic line iteration
47
+ while ((nl = buf.indexOf('\n')) !== -1) {
48
+ const line = buf.slice(0, nl + 1);
49
+ buf = buf.slice(nl + 1);
50
+ const result = redact(line, lineOpts);
51
+ out += result.text;
52
+ }
53
+ // Force-flush if the unterminated tail grows past the buffer cap
54
+ // (defends against unbounded line lengths from misbehaving tools).
55
+ if (buf.length >= bufferLimit) {
56
+ out += flushBufferThroughRedactor();
57
+ }
58
+ cb(null, out);
59
+ },
60
+ flush(cb) {
61
+ cb(null, flushBufferThroughRedactor());
62
+ },
63
+ });
64
+ }
65
+ //# sourceMappingURL=streaming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming.js","sourceRoot":"","sources":["../src/streaming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAA0B,MAAM,aAAa,CAAC;AAChE,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGtD;;;;;GAKG;AACH,MAAM,oBAAoB,GAAG,EAAE,GAAG,IAAI,CAAC;AAOvC;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAyB,EAAE;IAC5D,MAAM,QAAQ,GAAkB;QAC9B,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,qBAAqB;QAChD,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACpE,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACnD,CAAC;IACF,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,oBAAoB,CAAC;IAC7D,IAAI,GAAG,GAAG,EAAE,CAAC;IAEb,SAAS,0BAA0B;QACjC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACrC,GAAG,GAAG,EAAE,CAAC;QACT,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;IAED,OAAO,IAAI,SAAS,CAAC;QACnB,SAAS,CAAC,KAAa,EAAE,IAAI,EAAE,EAAqB;YAClD,GAAG,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;YAC9B,IAAI,GAAG,GAAG,EAAE,CAAC;YACb,IAAI,EAAU,CAAC;YACf,+EAA+E;YAC/E,OAAO,CAAC,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBACvC,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;gBAClC,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;gBACxB,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;gBACtC,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC;YACrB,CAAC;YACD,iEAAiE;YACjE,mEAAmE;YACnE,IAAI,GAAG,CAAC,MAAM,IAAI,WAAW,EAAE,CAAC;gBAC9B,GAAG,IAAI,0BAA0B,EAAE,CAAC;YACtC,CAAC;YACD,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAChB,CAAC;QACD,KAAK,CAAC,EAAqB;YACzB,EAAE,CAAC,IAAI,EAAE,0BAA0B,EAAE,CAAC,CAAC;QACzC,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=streaming.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming.test.d.ts","sourceRoot":"","sources":["../src/streaming.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,63 @@
1
+ import { Readable } from 'node:stream';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { createRedactStream } from './streaming.js';
4
+ // Same construction pattern as patterns.test.ts: build secret-shaped
5
+ // fixtures from concatenation so static scanners don't match the
6
+ // literals.
7
+ const X = (n) => 'X'.repeat(n);
8
+ const ghClassic = `${'ghp'}_${X(36)}`;
9
+ const awsKey = `AKIA${'EXAMPLE'.repeat(2)}EX`;
10
+ async function pipe(input, stream) {
11
+ const reader = Readable.from([Buffer.from(input, 'utf8')]);
12
+ const chunks = [];
13
+ for await (const chunk of reader.pipe(stream)) {
14
+ chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
15
+ }
16
+ return Buffer.concat(chunks).toString('utf8');
17
+ }
18
+ describe('createRedactStream', () => {
19
+ it('redacts an inline secret and preserves the rest of the line', async () => {
20
+ const out = await pipe('INFO connecting postgres://u:p@db/app for migration\n', createRedactStream({ entropy: { enabled: false } }));
21
+ expect(out).toContain('<REDACTED:postgres-uri>');
22
+ expect(out).toContain('INFO connecting');
23
+ expect(out).toContain('for migration');
24
+ });
25
+ it('flushes a trailing line that has no terminating newline', async () => {
26
+ const out = await pipe(`tail line ${ghClassic}`, createRedactStream({ entropy: { enabled: false } }));
27
+ expect(out).toContain('<REDACTED:github-pat-classic>');
28
+ });
29
+ it('passes innocuous output through unchanged', async () => {
30
+ const input = 'hello\nworld\n';
31
+ const out = await pipe(input, createRedactStream());
32
+ expect(out).toBe(input);
33
+ });
34
+ it('handles many small chunks without losing bytes', async () => {
35
+ const stream = createRedactStream({ entropy: { enabled: false } });
36
+ const input = `aws=${awsKey} done\n`;
37
+ const reader = Readable.from(input.split('').map((c) => Buffer.from(c, 'utf8')));
38
+ const chunks = [];
39
+ for await (const chunk of reader.pipe(stream)) {
40
+ chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
41
+ }
42
+ const out = Buffer.concat(chunks).toString('utf8');
43
+ expect(out).toContain('<REDACTED:aws-access-key-id>');
44
+ expect(out).toContain('aws=');
45
+ expect(out).toContain(' done');
46
+ });
47
+ it('does not apply multiline PEM block patterns (documented limitation)', async () => {
48
+ const input = [
49
+ 'log line one',
50
+ '-----BEGIN RSA PRIVATE KEY-----',
51
+ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
52
+ '-----END RSA PRIVATE KEY-----',
53
+ 'log line last',
54
+ '',
55
+ ].join('\n');
56
+ const out = await pipe(input, createRedactStream({ entropy: { enabled: false } }));
57
+ // Streaming mode does NOT redact across newlines — confirming the
58
+ // limitation. Per-line content remains, so callers must use the
59
+ // batch API when they need multi-line coverage.
60
+ expect(out).toContain('-----BEGIN RSA PRIVATE KEY-----');
61
+ });
62
+ });
63
+ //# sourceMappingURL=streaming.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming.test.js","sourceRoot":"","sources":["../src/streaming.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * A regex-based redaction pattern. Names are stable identifiers used
3
+ * in audit logs and the `pattern_summary` returned to MCP callers.
4
+ *
5
+ * `multiline` patterns may span newlines (e.g., RSA private-key blocks).
6
+ * They are NOT applied in streaming mode — only in batch — because
7
+ * detecting them across chunk boundaries requires unbounded buffering.
8
+ */
9
+ export interface Pattern {
10
+ readonly name: string;
11
+ readonly regex: RegExp;
12
+ readonly multiline?: boolean;
13
+ /** How to render the redaction string. Default: `<REDACTED:{name}>`. */
14
+ readonly redactWith?: (matched: string) => string;
15
+ }
16
+ /**
17
+ * Where a pattern was hit. Offsets are into the input string.
18
+ *
19
+ * `preview` is a tightly-bounded fragment (max 3 chars + `…`) — it lets
20
+ * audit consumers eyeball "what kind of secret was this" without
21
+ * exposing the full value. Never log the underlying matched string.
22
+ *
23
+ * Trust boundary: `preview` is informational only. It is derived from
24
+ * the matched string but truncated to at most 3 characters, making it
25
+ * insufficient to reconstruct the original value. Treat it as a
26
+ * non-sensitive hint for log readability, not as an authenticated
27
+ * identifier.
28
+ */
29
+ export interface Match {
30
+ readonly pattern: string;
31
+ readonly start: number;
32
+ readonly end: number;
33
+ readonly preview: string;
34
+ }
35
+ export interface RedactResult {
36
+ readonly text: string;
37
+ readonly matches: ReadonlyArray<Match>;
38
+ }
39
+ export interface RedactOptions {
40
+ /** Override the default pattern bank entirely. */
41
+ patterns?: ReadonlyArray<Pattern>;
42
+ /** Add patterns on top of the default bank. */
43
+ extraPatterns?: ReadonlyArray<Pattern>;
44
+ /** Exact-match strings to redact (e.g., known resolved values). */
45
+ literals?: ReadonlyArray<string>;
46
+ entropy?: EntropyOptions;
47
+ }
48
+ export interface EntropyOptions {
49
+ /** Default true. Disable to skip the entropy detector entirely. */
50
+ enabled?: boolean;
51
+ /** Minimum token length to consider. Default 24. */
52
+ minLength?: number;
53
+ /** Minimum bits-per-character. Default 4.5. */
54
+ minBitsPerChar?: number;
55
+ /**
56
+ * Token prefixes to exclude from entropy detection. Tokens whose
57
+ * lowercased value starts with any of these strings are never flagged.
58
+ * Default: common content-hash prefixes (sha1-, sha256:, sha384-,
59
+ * sha512-) which appear in package-lock.json integrity fields and
60
+ * Docker digests but are never actual secrets.
61
+ */
62
+ excludePrefixes?: ReadonlyArray<string>;
63
+ }
64
+ export interface RedactionSummary {
65
+ totalMatches: number;
66
+ byPattern: Record<string, number>;
67
+ }
68
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,wEAAwE;IACxE,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;CACnD;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,aAAa;IAC5B,kDAAkD;IAClD,QAAQ,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAClC,+CAA+C;IAC/C,aAAa,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IACvC,mEAAmE;IACnE,QAAQ,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@keynv/redactor",
3
+ "version": "0.1.0-rc.23",
4
+ "description": "keynv output and file redaction (regex + entropy + per-project custom patterns).",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "engines": {
19
+ "node": ">=20.0.0"
20
+ },
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/keynv-labs/keynv.git",
27
+ "directory": "packages/redactor"
28
+ },
29
+ "homepage": "https://github.com/keynv-labs/keynv#readme",
30
+ "bugs": {
31
+ "url": "https://github.com/keynv-labs/keynv/issues"
32
+ },
33
+ "scripts": {
34
+ "build": "tsc -p tsconfig.json",
35
+ "typecheck": "tsc -p tsconfig.json --noEmit",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest",
38
+ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\""
39
+ }
40
+ }