@mnemonik/shared 7.141.0 → 7.141.2

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.
@@ -0,0 +1,491 @@
1
+ const REDACTED = '<redacted credential>';
2
+ const PEM_BEGIN = '-----BEGIN ';
3
+ const MAX_PEM_LABEL_LENGTH = 64;
4
+ const MAX_PEM_BODY_LENGTH = 8 * 1024;
5
+ /**
6
+ * A password redacted where a rule recognised it (an sshpass command) is also
7
+ * redacted where the same string recurs (the same password named in prose),
8
+ * but only from this length up: a short value recurs in ordinary words by
9
+ * chance. See isDistinctiveValue for the other condition.
10
+ */
11
+ const MIN_REPEATED_VALUE_LENGTH = 6;
12
+ /**
13
+ * A value worth chasing mixes at least two of lower case, upper case, digits
14
+ * and other characters. A password that is a plain word ("postgres" in a local
15
+ * connection string, a project name) recurs as that word, in URLs and prose
16
+ * that are not about the password at all.
17
+ */
18
+ function isDistinctiveValue(value) {
19
+ const classes = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/].filter((re) => re.test(value));
20
+ return value.length >= MIN_REPEATED_VALUE_LENGTH && classes.length >= 2;
21
+ }
22
+ /**
23
+ * Every rule's value must hold a letter or a digit. Prose and code put
24
+ * punctuation where a value would go ("--password=; repeated copies",
25
+ * "API_KEY = ================", "Bearer ...", "user:***@"), and a run of
26
+ * punctuation is never a credential. Placeholders are judged separately, as
27
+ * before.
28
+ */
29
+ const LETTER_OR_DIGIT = /[A-Za-z0-9]/;
30
+ function hasLetterOrDigit(value) {
31
+ return LETTER_OR_DIGIT.test(value);
32
+ }
33
+ /** Replace the value of a `<context><value>` match, unless the value holds no letter or digit. */
34
+ function redactContextValue(match, head = '', value = '') {
35
+ return hasLetterOrDigit(value) ? `${head}${REDACTED}` : match;
36
+ }
37
+ // A URL password (connection string or HTTP basic auth), or a value found
38
+ // elsewhere, that is documentation, not a credential: a marker another redactor
39
+ // left, a template variable, a mask, a stock word, or a "your-api-key-here"
40
+ // phrase (production held one in consolidation history, 2026-10-13; chased, it
41
+ // would have replaced the phrase in a memory, a fact and two versions).
42
+ const PLACEHOLDER_PASSWORD = /^(?:\[REDACTED(?::[A-Za-z0-9_-]+)?\]|redacted|<[^<>]*>|\{\{[^{}]*\}\}|\$\{[^{}]+\}|%[A-Za-z_][A-Za-z0-9_]*%|\*+|x{3,}|\.\.\.|\u2026|password|passwd|pass|pwd|pw|secret|changeme|change[_-]me|example|test|(?:your|my)[a-z_-]*?(?:password|passwd|pass|pwd|secret|key|token|here))$/i;
43
+ // $VAR is matched case-sensitively: an upper-case name is an environment
44
+ // variable; a lower-case `$ecret1` could be a real password.
45
+ const PLACEHOLDER_ENV_VAR = /^\$[A-Z_][A-Z0-9_]*$/;
46
+ function isPlaceholderPassword(password) {
47
+ return PLACEHOLDER_PASSWORD.test(password) || PLACEHOLDER_ENV_VAR.test(password);
48
+ }
49
+ /** Replace the password of a `scheme://user:password@` match, keeping the rest. */
50
+ function redactUrlPassword(match, head = '', password = '') {
51
+ return isPlaceholderPassword(password) || !hasLetterOrDigit(password)
52
+ ? match
53
+ : `${head}${REDACTED}@`;
54
+ }
55
+ // A shell word in a password position: single-quoted, double-quoted, or bare.
56
+ // A bare word stops at whitespace, quotes, backticks and shell operators. It
57
+ // never starts on a marker this module left, so a second pass changes nothing,
58
+ // nor on a bracket, brace, slash, backslash or comma, which in that position is
59
+ // code (a regex or template under construction, an awk pattern such as
60
+ // `/^PGPASSWORD=/`), not a password.
61
+ const SHELL_WORD = String.raw `'[^'\n]*'|"[^"\n]*"|(?!${REDACTED})[^\s'"\`;|&()[\]{}\\,/][^\s'"\`;|&]*`;
62
+ // A quote that may open a string around the whole assignment or command
63
+ // (`"PGPASSWORD=" pw` in awk, `"--password=" + pw` in code).
64
+ const OPENING_QUOTE = String.raw `(["'\`]?)`;
65
+ // Trailing punctuation of a bare word in prose ("... sshpass -p <password>.").
66
+ const TRAILING_PUNCTUATION = /[.,:;!?)\]}]+$/;
67
+ // Words that follow a password flag in prose about the flag, not a password:
68
+ // "sshpass -p is unsafe", "pass it with sshpass -p in a script".
69
+ const PROSE_WORDS = new Set([
70
+ 'a',
71
+ 'an',
72
+ 'and',
73
+ 'as',
74
+ 'for',
75
+ 'in',
76
+ 'is',
77
+ 'it',
78
+ 'on',
79
+ 'or',
80
+ 'the',
81
+ 'to',
82
+ 'with',
83
+ 'flag',
84
+ 'option',
85
+ 'argument',
86
+ 'instead',
87
+ 'which',
88
+ 'that',
89
+ 'this',
90
+ 'puts',
91
+ 'passes',
92
+ 'leaks',
93
+ 'exposes',
94
+ ]);
95
+ // A regex delimiter inside a bare word: a `/` that ends it or that code
96
+ // punctuation follows (`abc/ {`, `abc/{print`, `abc/)`). An awk or sed pattern
97
+ // such as `/^PGPASSWORD=abc/ { ... }`, not a password.
98
+ const REGEX_DELIMITER = /\/(?:[{}()[\];]|$)/;
99
+ /**
100
+ * The password a shell word carries, or null when it carries none: an
101
+ * expansion (`$PASS`, `"$PASS"`, `"$(cat file)"`; only single quotes make `$`
102
+ * literal), a documentation placeholder, a word with no letter or digit (code
103
+ * punctuation, an ellipsis), a quoted word that starts or ends with whitespace
104
+ * (its first quote closed a string, as in `"PGPASSWORD=" pw; ... "`), a bare
105
+ * word ending at a regex delimiter, or a prose word.
106
+ */
107
+ function shellWordSecret(word) {
108
+ const quote = word[0];
109
+ if ((quote === "'" || quote === '"') && word.length >= 2 && word.endsWith(quote)) {
110
+ const inner = word.slice(1, -1);
111
+ if (!hasLetterOrDigit(inner) || (quote === '"' && inner.startsWith('$')))
112
+ return null;
113
+ if (inner !== inner.trim())
114
+ return null;
115
+ return isPlaceholderPassword(inner) ? null : inner;
116
+ }
117
+ if (word.startsWith('$'))
118
+ return null;
119
+ const core = word.replace(TRAILING_PUNCTUATION, '');
120
+ if (!hasLetterOrDigit(core) || isPlaceholderPassword(core))
121
+ return null;
122
+ if (REGEX_DELIMITER.test(core))
123
+ return null;
124
+ return PROSE_WORDS.has(core.toLowerCase()) ? null : core;
125
+ }
126
+ /** Replace the password word of a command, keeping the command around it. */
127
+ function redactShellWord(match, head = '', word = '', tail = '') {
128
+ return shellWordSecret(word) === null ? match : `${head}${REDACTED}${tail}`;
129
+ }
130
+ /**
131
+ * As redactShellWord, for a rule that may start inside a string: when the
132
+ * assignment or option is itself quoted and its value position holds the same
133
+ * quote (`"PGPASSWORD=" pw`, `'--password=' + pw`), that quote closes the
134
+ * string; the program supplies the value from a variable.
135
+ */
136
+ function redactQuotedShellWord(match, head = '', openingQuote = '', word = '', tail = '') {
137
+ if (openingQuote !== '' && word.startsWith(openingQuote))
138
+ return match;
139
+ return redactShellWord(match, head, word, tail);
140
+ }
141
+ // The programs sshpass is run in front of. A bare lower-case word after -p is
142
+ // taken for a password only when one of these follows it.
143
+ const SSHPASS_COMMAND = String.raw `ssh|scp|sftp|rsync|ssh-copy-id|ansible[\w-]*`;
144
+ /**
145
+ * As redactShellWord, for `sshpass -p`, whose flag agents also write about
146
+ * ("the sshpass -p form, ...", "rules for sshpass -p passwords"): a bare
147
+ * all-lower-case word counts as a password only when the command sshpass runs
148
+ * follows it. Every sshpass password in production on 2026-10-12 (63 copies)
149
+ * was followed by ssh or scp; the one prose mention was a lower-case word
150
+ * followed by more prose. A lower-case password at the end of a line is the
151
+ * miss this accepts.
152
+ */
153
+ function redactSshpassWord(match, head = '', openingQuote = '', word = '', tail = '', command = '') {
154
+ if (openingQuote !== '' && word.startsWith(openingQuote))
155
+ return match;
156
+ const secret = shellWordSecret(word);
157
+ if (secret === null)
158
+ return match;
159
+ const bare = word[0] !== "'" && word[0] !== '"';
160
+ if (bare && /^[a-z]+$/.test(secret) && command === '')
161
+ return match;
162
+ return `${head}${REDACTED}${tail}`;
163
+ }
164
+ const PATTERNS = [
165
+ {
166
+ kind: 'bearer',
167
+ pattern: /(\bBearer\s+)([^\s]{40,})/gi,
168
+ replacement: redactContextValue,
169
+ secretGroup: 2,
170
+ },
171
+ {
172
+ // Before openai, whose sk- shape would otherwise claim these keys.
173
+ kind: 'anthropic',
174
+ pattern: /(?<![A-Za-z0-9_-])sk-ant-(?=[_-]*[A-Za-z0-9])[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g,
175
+ replacement: REDACTED,
176
+ },
177
+ {
178
+ kind: 'openai',
179
+ pattern: /(?<![A-Za-z0-9_-])sk-(?:proj-)?(?=[_-]*[A-Za-z0-9])[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_-])/g,
180
+ replacement: REDACTED,
181
+ },
182
+ {
183
+ kind: 'shopify',
184
+ pattern: /(?<![A-Za-z0-9_])shp(?:at|ss|ca)_[A-Za-z0-9]{16,}(?![A-Za-z0-9])/g,
185
+ replacement: REDACTED,
186
+ },
187
+ {
188
+ kind: 'aws_access_key',
189
+ pattern: /(?<![A-Z0-9])AKIA[A-Z0-9]{16}(?![A-Z0-9])/g,
190
+ replacement: REDACTED,
191
+ },
192
+ {
193
+ kind: 'github',
194
+ pattern: /(?<![A-Za-z0-9_])gh[opu]_[A-Za-z0-9]{20,}(?![A-Za-z0-9])/g,
195
+ replacement: REDACTED,
196
+ },
197
+ {
198
+ kind: 'slack',
199
+ pattern: /(?<![A-Za-z0-9-])xox[abp]-(?=-*[A-Za-z0-9])[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g,
200
+ replacement: REDACTED,
201
+ },
202
+ {
203
+ kind: 'sentry',
204
+ pattern: /(?<![A-Za-z0-9_])sntry[us]_(?=[_-]*[A-Za-z0-9])[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_-])/g,
205
+ replacement: REDACTED,
206
+ },
207
+ {
208
+ kind: 'fly',
209
+ pattern: /(?<![A-Za-z0-9_])(?:FlyV1\s+)?fm2_(?=[_-]*[A-Za-z0-9])[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_-])/g,
210
+ replacement: REDACTED,
211
+ },
212
+ {
213
+ kind: 'google_api_key',
214
+ pattern: /(?<![A-Za-z0-9_-])AIza(?=[_-]*[A-Za-z0-9])[0-9A-Za-z_-]{35}(?![A-Za-z0-9_-])/g,
215
+ replacement: REDACTED,
216
+ },
217
+ {
218
+ kind: 'jwt',
219
+ // header.payload.signature, both JSON segments starting eyJ. The length
220
+ // floors keep `eyJ...` prose and signature-less examples out.
221
+ pattern: /(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{16,}(?![A-Za-z0-9_-])/g,
222
+ replacement: REDACTED,
223
+ },
224
+ {
225
+ kind: 'mnemonik_component_token',
226
+ // mnc_ / mncr_ + 43 base64url (oauth/components.ts); 32+ also catches a
227
+ // partial token. The body must contain an upper-case letter or digit, so a
228
+ // snake_case identifier is not mistaken for one; the 8-character
229
+ // display prefix (mnc_ + 8) is too short.
230
+ pattern: /(?<![A-Za-z0-9_])mncr?_(?=[A-Za-z0-9_-]*[A-Z0-9])[A-Za-z0-9_-]{32,}/g,
231
+ replacement: REDACTED,
232
+ },
233
+ {
234
+ kind: 'stripe',
235
+ // Secret and restricted keys (sk_/rk_ live|test) and webhook signing secrets.
236
+ pattern: /(?<![A-Za-z0-9_])(?:(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}|whsec_[A-Za-z0-9]{20,})(?![A-Za-z0-9])/g,
237
+ replacement: REDACTED,
238
+ },
239
+ {
240
+ kind: 'mnemonik_proxy_token',
241
+ // pxt_ + 32 hex (the format server.ts validates). A bare prefix in prose is left.
242
+ pattern: /(?<![A-Za-z0-9_])pxt_[0-9a-f]{32,}/g,
243
+ replacement: REDACTED,
244
+ },
245
+ {
246
+ kind: 'mnemonik_api_key',
247
+ // mnk_ + 64 hex (AuthManager.generateApiKey); 32+ also catches a half key.
248
+ // The stored 12-character key_prefix (mnk_ + 8 hex) is not a credential.
249
+ pattern: /(?<![A-Za-z0-9_])mnk_[0-9a-f]{32,}/g,
250
+ replacement: REDACTED,
251
+ },
252
+ {
253
+ kind: 'mnemonik_api_key_legacy',
254
+ // mnk_ + 43 base64url: keys minted by hand before AuthManager's hex form
255
+ // (production's only key in 2026-10 has this shape, and one stored memory
256
+ // held a development key in it). Authentication accepts any mnk_ bearer.
257
+ // The body needs upper and lower case and a digit, so a snake_case
258
+ // identifier that starts mnk_ is not taken for one.
259
+ pattern: /(?<![A-Za-z0-9_])mnk_(?=[A-Za-z0-9_-]*[A-Z])(?=[A-Za-z0-9_-]*[a-z])(?=[A-Za-z0-9_-]*[0-9])[A-Za-z0-9_-]{40,}(?![A-Za-z0-9_-])/g,
260
+ replacement: REDACTED,
261
+ },
262
+ {
263
+ kind: 'named_value',
264
+ pattern: /(\b[A-Za-z_][A-Za-z0-9_]*(?:_TOKEN|_KEY|_SECRET|_PASSWORD|_PASS|APIKEY|API_KEY)\s*(?:=|:)\s*)([^\s]{16,})/gi,
265
+ replacement: redactContextValue,
266
+ secretGroup: 2,
267
+ },
268
+ {
269
+ kind: 'connection_string',
270
+ // scheme://user:password@host: only the password is replaced; scheme,
271
+ // user, host and database stay readable. The user may be empty
272
+ // (redis://:password@host). The password runs to the last `@` before a
273
+ // path, query, fragment, quote or whitespace, so an unencoded `@` inside
274
+ // it is still covered. Runs after named_value, which already replaces a
275
+ // whole URL assigned to a secret-named variable.
276
+ pattern: /\b((?:postgres(?:ql)?|mysql|mariadb|mongodb|rediss?|amqps?|mssql|sqlserver)(?:\+[a-z0-9]+)?:\/\/[^\s:/?#@'"`<>]{0,256}:)([^\s/?#'"`]{1,512})@/gi,
277
+ replacement: redactUrlPassword,
278
+ secretGroup: 2,
279
+ },
280
+ {
281
+ kind: 'url_basic_auth',
282
+ // https://user:password@host: same shape and placeholder rules as a
283
+ // connection string; only the password is replaced.
284
+ pattern: /\b(https?:\/\/[^\s:/?#@'"`<>]{0,256}:)([^\s/?#'"`]{1,512})@/gi,
285
+ replacement: redactUrlPassword,
286
+ secretGroup: 2,
287
+ },
288
+ // Command-line passwords. Each keeps the command and replaces only the
289
+ // password word; an expansion or placeholder in that position is left.
290
+ {
291
+ // sshpass -p <password> (also -p'<password>' and -p"<password>"), after
292
+ // any other sshpass flags. 13 memories, 11 scanner chunks, 15 versions and
293
+ // 6 dated facts in production held one on 2026-10-12.
294
+ kind: 'sshpass_password',
295
+ pattern: new RegExp(String.raw `(${OPENING_QUOTE}\bsshpass(?:[ \t]+-[a-oq-zA-Z]\S*)*[ \t]+-p[ \t]*)(${SHELL_WORD})()(?=((?:[ \t]+(?:${SSHPASS_COMMAND})(?![\w-]))?))`, 'g'),
296
+ replacement: redactSshpassWord,
297
+ secretGroup: 3,
298
+ commandLine: true,
299
+ },
300
+ {
301
+ // Environment variables whose only purpose is a password, in shell
302
+ // assignment form (no space around `=`): libpq's PGPASSWORD (one memory,
303
+ // version and fact in production), MySQL's MYSQL_PWD, sshpass -e's
304
+ // SSHPASS. named_value needs an `_PASSWORD`-style suffix and 16
305
+ // characters, so it misses all three. Not an awk program that matches or
306
+ // prints the assignment: `/^PGPASSWORD=/ { print "PGPASSWORD=" pw }`.
307
+ kind: 'password_env_var',
308
+ pattern: new RegExp(String.raw `(${OPENING_QUOTE}\b(?:PGPASSWORD|MYSQL_PWD|SSHPASS)=)(${SHELL_WORD})()`, 'g'),
309
+ replacement: redactQuotedShellWord,
310
+ secretGroup: 3,
311
+ commandLine: true,
312
+ },
313
+ {
314
+ // --password=<value>, the long option every CLI that takes one spells
315
+ // this way. The `=` keeps prose about the option ("--password is
316
+ // required") out; the spaced form is not matched for that reason.
317
+ kind: 'password_option',
318
+ pattern: new RegExp(String.raw `(${OPENING_QUOTE}(?<![\w-])--password=)(${SHELL_WORD})()`, 'g'),
319
+ replacement: redactQuotedShellWord,
320
+ secretGroup: 3,
321
+ commandLine: true,
322
+ },
323
+ {
324
+ // echo <password> | sudo -S: sudo reads the password from stdin. One
325
+ // memory, two versions and two facts in production.
326
+ kind: 'sudo_password',
327
+ pattern: new RegExp(String.raw `(\becho[ \t]+(?:-[neE]+[ \t]+)?)(${SHELL_WORD})([ \t]*\|[ \t]*sudo\b[^|\n]*?[ \t](?:-[A-Za-z]*S[A-Za-z]*|--stdin)\b)`, 'g'),
328
+ replacement: redactShellWord,
329
+ secretGroup: 2,
330
+ commandLine: true,
331
+ },
332
+ ];
333
+ function isPrivateKeyLabel(label) {
334
+ if (label === 'PRIVATE KEY')
335
+ return true;
336
+ if (!label.endsWith(' PRIVATE KEY'))
337
+ return false;
338
+ const prefix = label.slice(0, -' PRIVATE KEY'.length);
339
+ return prefix.split(' ').every((word) => word.length > 0 &&
340
+ [...word].every((char) => {
341
+ const code = char.charCodeAt(0);
342
+ return (code >= 48 && code <= 57) || (code >= 65 && code <= 90);
343
+ }));
344
+ }
345
+ /** Linear, bounded PEM scanner. Unterminated or oversized blocks are preserved. */
346
+ function redactPemPrivateKeys(text) {
347
+ const pieces = [];
348
+ let copiedThrough = 0;
349
+ let cursor = 0;
350
+ let redactions = 0;
351
+ while (cursor < text.length) {
352
+ const begin = text.indexOf(PEM_BEGIN, cursor);
353
+ if (begin === -1)
354
+ break;
355
+ const labelStart = begin + PEM_BEGIN.length;
356
+ const headerEnd = text.indexOf('-----', labelStart);
357
+ if (headerEnd === -1 || headerEnd - labelStart > MAX_PEM_LABEL_LENGTH) {
358
+ cursor = labelStart;
359
+ continue;
360
+ }
361
+ const label = text.slice(labelStart, headerEnd);
362
+ if (!isPrivateKeyLabel(label)) {
363
+ cursor = headerEnd + 5;
364
+ continue;
365
+ }
366
+ const bodyStart = headerEnd + 5;
367
+ const endMarker = `-----END ${label}-----`;
368
+ const nextBegin = text.indexOf(PEM_BEGIN, bodyStart);
369
+ const boundedEnd = Math.min(text.length, bodyStart + MAX_PEM_BODY_LENGTH + endMarker.length);
370
+ const scanEnd = nextBegin === -1 ? boundedEnd : Math.min(nextBegin, boundedEnd);
371
+ const endOffset = text.slice(bodyStart, scanEnd).indexOf(endMarker);
372
+ if (endOffset >= 0 && endOffset <= MAX_PEM_BODY_LENGTH) {
373
+ const end = bodyStart + endOffset + endMarker.length;
374
+ pieces.push(text.slice(copiedThrough, begin), REDACTED);
375
+ copiedThrough = end;
376
+ cursor = end;
377
+ redactions++;
378
+ continue;
379
+ }
380
+ cursor = scanEnd > begin ? scanEnd : labelStart;
381
+ }
382
+ if (redactions === 0)
383
+ return { text, redactions };
384
+ pieces.push(text.slice(copiedThrough));
385
+ return { text: pieces.join(''), redactions };
386
+ }
387
+ /**
388
+ * The value a rule redacted, in the form worth chasing elsewhere in the text,
389
+ * or null when it is too short or too plain (isDistinctiveValue), a
390
+ * placeholder, or part of this module's own marker.
391
+ */
392
+ function chaseableValue(raw, commandLine) {
393
+ let value = raw;
394
+ if (commandLine)
395
+ value = shellWordSecret(raw);
396
+ else if (/^(["'`]).+\1$/.test(raw))
397
+ value = raw.slice(1, -1);
398
+ if (value === null || !isDistinctiveValue(value))
399
+ return null;
400
+ if (isPlaceholderPassword(value) || REDACTED.includes(value))
401
+ return null;
402
+ return value;
403
+ }
404
+ function escapeRegExp(value) {
405
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
406
+ }
407
+ /**
408
+ * Redact every recurrence of `values` that stands as a whole word (not inside
409
+ * a longer run of letters, digits or underscores), outside the markers already
410
+ * in the text.
411
+ */
412
+ function redactRecurrences(text, values) {
413
+ const present = [...values].filter((value) => text.includes(value));
414
+ if (present.length === 0)
415
+ return { text, redactions: 0 };
416
+ // Longest first, so a value that contains another is replaced whole.
417
+ present.sort((a, b) => b.length - a.length);
418
+ const pattern = new RegExp(`(?<![A-Za-z0-9_])(?:${present.map(escapeRegExp).join('|')})(?![A-Za-z0-9_])`, 'g');
419
+ let redactions = 0;
420
+ const pieces = text.split(REDACTED).map((piece) => piece.replace(pattern, () => {
421
+ redactions++;
422
+ return REDACTED;
423
+ }));
424
+ return { text: pieces.join(REDACTED), redactions };
425
+ }
426
+ function redact(text, options, values) {
427
+ const commandLineOnly = options.commandLineOnly === true;
428
+ const pem = commandLineOnly ? { text, redactions: 0 } : redactPemPrivateKeys(text);
429
+ let redactedText = pem.text;
430
+ let redactions = pem.redactions;
431
+ const kinds = pem.redactions > 0 ? ['pem_private_key'] : [];
432
+ for (const { kind, pattern, replacement, secretGroup, commandLine } of PATTERNS) {
433
+ if (commandLineOnly && !commandLine)
434
+ continue;
435
+ let matches = 0;
436
+ if (typeof replacement === 'string') {
437
+ matches = redactedText.match(pattern)?.length ?? 0;
438
+ redactedText = redactedText.replace(pattern, replacement);
439
+ }
440
+ else {
441
+ redactedText = redactedText.replace(pattern, (match, ...args) => {
442
+ // args = [...captureGroups, offset, input]; a non-participating group is undefined.
443
+ const offsetIndex = args.findIndex((arg) => typeof arg === 'number');
444
+ const groups = args
445
+ .slice(0, offsetIndex)
446
+ .map((group) => (typeof group === 'string' ? group : ''));
447
+ const next = replacement(match, ...groups);
448
+ if (next !== match) {
449
+ matches++;
450
+ const secret = secretGroup ? groups[secretGroup - 1] : undefined;
451
+ const value = secret ? chaseableValue(secret, commandLine === true) : null;
452
+ if (value !== null)
453
+ values.add(value);
454
+ }
455
+ return next;
456
+ });
457
+ }
458
+ if (matches > 0) {
459
+ redactions += matches;
460
+ kinds.push(kind);
461
+ }
462
+ }
463
+ const chase = new Set(values);
464
+ for (const known of options.knownValues ?? []) {
465
+ if (isDistinctiveValue(known) && !isPlaceholderPassword(known) && !REDACTED.includes(known)) {
466
+ chase.add(known);
467
+ }
468
+ }
469
+ const repeated = redactRecurrences(redactedText, chase);
470
+ if (repeated.redactions > 0) {
471
+ redactedText = repeated.text;
472
+ redactions += repeated.redactions;
473
+ kinds.push('repeated_credential');
474
+ }
475
+ return { text: redactedText, redactions, kinds };
476
+ }
477
+ export function redactCredentials(text, options = {}) {
478
+ return redact(text, options, new Set());
479
+ }
480
+ /**
481
+ * The credential values the context rules (command-line passwords, URL
482
+ * passwords, named values, Bearer tokens) would redact from `text`, for a
483
+ * caller that redacts them across many texts (`knownValues`). The values are
484
+ * secrets: never log, print, store or return them.
485
+ */
486
+ export function credentialValues(text, options = {}) {
487
+ const values = new Set();
488
+ redact(text, options, values);
489
+ return values;
490
+ }
491
+ //# sourceMappingURL=redactCredentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redactCredentials.js","sourceRoot":"","sources":["../src/redactCredentials.ts"],"names":[],"mappings":"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"}
@@ -40,6 +40,9 @@
40
40
  * guarded against hashes, UUIDs, paths, slugs and identifiers.
41
41
  */
42
42
  export declare const SECRET_REDACTION_PLACEHOLDER = "[REDACTED]";
43
+ export declare const SECRET_LABEL_RE: RegExp;
44
+ /** Keep physical and escaped newline sequences, including JS line continuations. */
45
+ export declare function redactSecretValue(value: string): string;
43
46
  export declare const SECRET_PATTERNS: ReadonlyArray<RegExp>;
44
47
  /**
45
48
  * Redact standalone high-entropy tokens that no provider pattern claimed.
@@ -1 +1 @@
1
- {"version":3,"file":"secretPatterns.d.ts","sourceRoot":"","sources":["../src/secretPatterns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,eAAO,MAAM,4BAA4B,eAAe,CAAC;AASzD,eAAO,MAAM,eAAe,EAAE,aAAa,CAAC,MAAM,CAoDjD,CAAC;AA4HF;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAc5D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWjD"}
1
+ {"version":3,"file":"secretPatterns.d.ts","sourceRoot":"","sources":["../src/secretPatterns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,eAAO,MAAM,4BAA4B,eAAe,CAAC;AAGzD,eAAO,MAAM,eAAe,QAA6D,CAAC;AAM1F,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKvD;AA0ED,eAAO,MAAM,eAAe,EAAE,aAAa,CAAC,MAAM,CAiDjD,CAAC;AA4HF;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAc5D;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAejD"}
@@ -40,6 +40,83 @@
40
40
  * guarded against hashes, UUIDs, paths, slugs and identifiers.
41
41
  */
42
42
  export const SECRET_REDACTION_PLACEHOLDER = '[REDACTED]';
43
+ const SECRET_LABEL = String.raw `[\w$-]*(?:api[_-]?key|secret|token|password|credential|auth)|aws_secret_access_key`;
44
+ export const SECRET_LABEL_RE = new RegExp(`^(?:${SECRET_LABEL}|MYSQL_PWD|SSHPASS)$`, 'i');
45
+ const LABELED_VALUE_RE = new RegExp(String.raw `(?<![\w$-])((["']?)(?:${SECRET_LABEL})\2\s*[:=]\s*)(?=\S)`, 'gi');
46
+ /** Keep physical and escaped newline sequences, including JS line continuations. */
47
+ export function redactSecretValue(value) {
48
+ return (SECRET_REDACTION_PLACEHOLDER +
49
+ (value.match(/\\(?:\r\n|[\r\n])|\r\n|[\r\n]|\\[rn]/g)?.join('') ?? ''));
50
+ }
51
+ function redactLabeledValues(text) {
52
+ let result = '';
53
+ let offset = 0;
54
+ LABELED_VALUE_RE.lastIndex = 0;
55
+ for (const match of text.matchAll(LABELED_VALUE_RE)) {
56
+ if (match.index < offset)
57
+ continue;
58
+ const start = match.index + match[0].length;
59
+ const escaped = text[start] === '\\' && /^["']/.test(text.charAt(start + 1));
60
+ const quote = escaped
61
+ ? text.charAt(start + 1)
62
+ : /^["'`]/.test(text.charAt(start))
63
+ ? text.charAt(start)
64
+ : '';
65
+ const delimiter = (escaped ? '\\' : '') + quote;
66
+ let end = start;
67
+ let closed = false;
68
+ if (quote) {
69
+ end += delimiter.length;
70
+ while (end < text.length) {
71
+ if (text[end] === '\\') {
72
+ if (escaped) {
73
+ const begin = end;
74
+ while (text[end] === '\\')
75
+ end++;
76
+ // A quoted value embedded in a string: \" closes, \\\" escapes its quote.
77
+ if (text[end] === quote && (end - begin) % 4 === 1) {
78
+ end++;
79
+ closed = true;
80
+ break;
81
+ }
82
+ end = Math.min(end + 1, text.length);
83
+ }
84
+ else {
85
+ end = Math.min(end + 2, text.length);
86
+ }
87
+ }
88
+ else if (text[end++] === quote && !escaped) {
89
+ closed = true;
90
+ break;
91
+ }
92
+ }
93
+ }
94
+ else {
95
+ // Never split an escape pair and leave its quote as executable syntax.
96
+ end += /^(?:\\[^\r\n]|[^\s"'`,;()[\]{}\\])+/.exec(text.slice(start))?.[0].length ?? 0;
97
+ }
98
+ const value = text.slice(start, end);
99
+ if (!/[A-Za-z0-9]/.test(value) || ENV_READ_VALUE_RE.test(match[0] + value))
100
+ continue;
101
+ // The quote closes a command string or anchored label search, such as '^API_KEY='.
102
+ if (quote &&
103
+ !escaped &&
104
+ (text[match.index - 1] === quote ||
105
+ (text[match.index - 1] === '^' &&
106
+ text[match.index - 2] === quote &&
107
+ (start + 1 === text.length || /\s/.test(text.charAt(start + 1))))))
108
+ continue;
109
+ if (/aws_secret_access_key/i.test(match[0]) && /^<[^>]*>$/.test(value))
110
+ continue;
111
+ const body = quote
112
+ ? value.slice(delimiter.length, closed ? -delimiter.length : undefined)
113
+ : value.replace(/(?:\*\/|[.!?]+)$/, '');
114
+ const tail = quote ? (closed ? delimiter : '') : value.slice(body.length);
115
+ result += text.slice(offset, start) + delimiter + redactSecretValue(body) + tail;
116
+ offset = end;
117
+ }
118
+ return result + text.slice(offset);
119
+ }
43
120
  // Shared by the scanner daemon and server-side scrub paths.
44
121
  //
45
122
  // A value must hold a letter or a digit, in every pattern whose value is free
@@ -48,7 +125,7 @@ export const SECRET_REDACTION_PLACEHOLDER = '[REDACTED]';
48
125
  // flag: "--password=; repeated copies" in a stored checkpoint became
49
126
  // "--[REDACTED] repeated copies" (2026-10-13), and so did "password: ***".
50
127
  export const SECRET_PATTERNS = [
51
- /(?:api[_-]?key|secret|token|password|credential|auth)\s*[:=]\s*(?=\S*[A-Za-z0-9])\S+/gi,
128
+ LABELED_VALUE_RE, // Select the label; the linear reader consumes its complete value.
52
129
  // Stripe-shape: (sk|pk)_(live|test)_<24+ alphanumerics>. Catches modern
53
130
  // Stripe keys whose body is split by an environment separator that
54
131
  // breaks the contiguous-alphanum pattern below. Required `live|test`
@@ -69,10 +146,7 @@ export const SECRET_PATTERNS = [
69
146
  // \b-delimited, so prose ("AKIAMIA is a place name") cannot match.
70
147
  /\bAKIA[0-9A-Z]{16}\b/g,
71
148
  /\bASIA[0-9A-Z]{16}\b/g,
72
- // AWS secret access key, labeled. The generic key=value pattern above does
73
- // NOT cover this: its `secret` alternative must be followed immediately by
74
- // `[:=]`, and here it is followed by `_access_key`.
75
- /\baws_secret_access_key\s*[:=]\s*[A-Za-z0-9/+=]{40}\b/gi,
149
+ // Labeled AWS secret access keys are covered by LABELED_VALUE_RE, including quotes.
76
150
  // JWT: three base64url segments. Length minimums keep dotted identifiers
77
151
  // (`payload.header.signature`) out.
78
152
  /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{5,}\b/g,
@@ -252,7 +326,12 @@ export function scrubSecrets(text) {
252
326
  return text;
253
327
  let result = text;
254
328
  for (const pattern of SECRET_PATTERNS) {
255
- result = result.replace(pattern, (match) => ENV_READ_VALUE_RE.test(match) ? match : SECRET_REDACTION_PLACEHOLDER);
329
+ if (pattern === LABELED_VALUE_RE) {
330
+ result = redactLabeledValues(result);
331
+ }
332
+ else {
333
+ result = result.replace(pattern, (match) => ENV_READ_VALUE_RE.test(match) ? match : redactSecretValue(match));
334
+ }
256
335
  }
257
336
  // Backstop for credentials with no recognizable prefix. Runs last so the
258
337
  // precise provider patterns get first claim on their own shapes.