@code-yeongyu/senpi-ai 2026.10.26-2 → 2026.10.27-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.
Files changed (34) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.js +2 -0
  3. package/dist/providers/data/.manifest.json +1 -1
  4. package/dist/providers/data/azure-openai-responses.json +1 -1
  5. package/dist/providers/data/github-copilot.json +1 -1
  6. package/dist/providers/data/huggingface.json +1 -1
  7. package/dist/providers/data/moonshotai-cn.json +1 -1
  8. package/dist/providers/data/moonshotai.json +1 -1
  9. package/dist/providers/data/openai.json +1 -1
  10. package/dist/providers/data/opencode.json +1 -1
  11. package/dist/providers/data/opengateway.json +1 -1
  12. package/dist/providers/data/openrouter.json +1 -1
  13. package/dist/providers/data/radius.json +1 -1
  14. package/dist/providers/data/venice.json +1 -1
  15. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  16. package/dist/types.d.ts +12 -1
  17. package/dist/utils/assistant-capture-record.d.ts +13 -0
  18. package/dist/utils/assistant-capture-record.js +163 -0
  19. package/dist/utils/assistant-capture.d.ts +10 -4
  20. package/dist/utils/assistant-capture.js +2 -159
  21. package/dist/utils/assistant-seed-frontier.d.ts +7 -0
  22. package/dist/utils/assistant-seed-frontier.js +14 -0
  23. package/dist/utils/event-stream.d.ts +1 -0
  24. package/dist/utils/event-stream.js +13 -0
  25. package/dist/utils/provider-error.d.ts +12 -0
  26. package/dist/utils/provider-error.js +28 -0
  27. package/dist/utils/retry.js +3 -0
  28. package/dist/utils/secret-classifier.d.ts +28 -0
  29. package/dist/utils/secret-classifier.js +258 -0
  30. package/dist/utils/secret-lexer.d.ts +51 -0
  31. package/dist/utils/secret-lexer.js +295 -0
  32. package/dist/utils/secret-redaction.d.ts +13 -0
  33. package/dist/utils/secret-redaction.js +198 -0
  34. package/package.json +2 -2
@@ -0,0 +1,258 @@
1
+ // What the credential redactor recognizes (senpi#4283): the credential words a name ends with, and the shapes of known
2
+ // key prefixes and JWTs. Each check reads only the characters of the token it is given, forward, without a regular
3
+ // expression.
4
+ import { isAlnum, isBase64Url, isDigit, isLower, isNameChar, isUpper, matchesAt, readUnit } from "./secret-lexer.js";
5
+ /** The last component of a credential name, after digits and qualifiers are stripped (see secretWord). */
6
+ const SECRET_WORDS = new Set([
7
+ "key",
8
+ "apikey",
9
+ "accesskey",
10
+ "secretkey",
11
+ "privatekey",
12
+ "token",
13
+ "accesstoken",
14
+ "refreshtoken",
15
+ "authtoken",
16
+ "apitoken",
17
+ "idtoken",
18
+ "secret",
19
+ "clientsecret",
20
+ "auth",
21
+ "authorization",
22
+ "bearer",
23
+ "cookie",
24
+ "cookies",
25
+ "password",
26
+ "passwd",
27
+ "pwd",
28
+ "passphrase",
29
+ "credential",
30
+ "credentials",
31
+ "signature",
32
+ "sig",
33
+ ]);
34
+ /** Names whose whole value is a credential at any length; every other credential name needs a 16-character value. */
35
+ export const STRICT_WORDS = new Set([
36
+ "password",
37
+ "passwd",
38
+ "pwd",
39
+ "passphrase",
40
+ "secret",
41
+ "clientsecret",
42
+ "authorization",
43
+ "bearer",
44
+ ]);
45
+ export const COOKIE_WORDS = new Set(["cookie", "cookies"]);
46
+ /** A trailing component that names a part of the credential the name before it holds: api_key_value, apiKeyId. */
47
+ const QUALIFIERS = new Set(["value", "val", "id"]);
48
+ export const SCHEMES = new Set(["bearer", "basic", "token", "apikey"]);
49
+ /** Values that are never credentials, including a replacement an earlier pass wrote (redaction is idempotent). */
50
+ const INERT_VALUES = new Set(["redacted", "null", "true", "false", "none", "undefined"]);
51
+ export const COOKIE_ATTRIBUTES = new Set([
52
+ "path",
53
+ "domain",
54
+ "expires",
55
+ "max-age",
56
+ "samesite",
57
+ "secure",
58
+ "httponly",
59
+ "priority",
60
+ "partitioned",
61
+ ]);
62
+ const isUpperOrDigit = (c) => isDigit(c) || isUpper(c);
63
+ const isSlackChar = (c) => isAlnum(c) || c === 45;
64
+ const isPatChar = (c) => isAlnum(c) || c === 95;
65
+ const isWordChar = (c) => isAlnum(c) || c === 95;
66
+ /** Split a name into lowercase components on "_", "-", "." and camelCase, reading only the name's own characters. */
67
+ function nameComponents(text, start, end) {
68
+ const parts = [];
69
+ let current = "";
70
+ for (let i = start; i < end; i++) {
71
+ const c = text.charCodeAt(i);
72
+ if (c === 95 || c === 45 || c === 46) {
73
+ if (current.length > 0)
74
+ parts.push(current);
75
+ current = "";
76
+ continue;
77
+ }
78
+ if (isUpper(c) && current.length > 0) {
79
+ const previous = text.charCodeAt(i - 1);
80
+ const next = i + 1 < end ? text.charCodeAt(i + 1) : 0;
81
+ if (isLower(previous) || isDigit(previous) || (isUpper(previous) && isLower(next))) {
82
+ parts.push(current);
83
+ current = "";
84
+ }
85
+ }
86
+ current += String.fromCharCode(isUpper(c) ? c + 32 : c);
87
+ }
88
+ if (current.length > 0)
89
+ parts.push(current);
90
+ return parts;
91
+ }
92
+ function isAllDigits(part) {
93
+ for (let k = 0; k < part.length; k++)
94
+ if (!isDigit(part.charCodeAt(k)))
95
+ return false;
96
+ return true;
97
+ }
98
+ function withoutTrailingDigits(part) {
99
+ let end = part.length;
100
+ while (end > 1 && isDigit(part.charCodeAt(end - 1)))
101
+ end--;
102
+ return part.slice(0, end);
103
+ }
104
+ /**
105
+ * The credential word a name ends with, after its normalization: numbered slots (OPENAI_API_KEY_2, token2,
106
+ * x-api-key-1) and a trailing qualifier (api_key_value, apiKeyId) still end with the word. "session" counts only as
107
+ * the whole name, so session_id stays readable; a word elsewhere in a name (token_type, primary_key_column) or inside
108
+ * a word (monkey, tokenizer, max_tokens) does not count.
109
+ */
110
+ export function secretWord(text, start, end) {
111
+ const parts = nameComponents(text, start, end);
112
+ if (parts.length === 1 && parts[0] === "session")
113
+ return "session";
114
+ let last = parts.length - 1;
115
+ for (;;) {
116
+ while (last >= 0 && isAllDigits(parts[last] ?? ""))
117
+ last--;
118
+ if (last > 0 && QUALIFIERS.has(parts[last] ?? ""))
119
+ last--;
120
+ else
121
+ break;
122
+ }
123
+ if (last < 0)
124
+ return undefined;
125
+ const word = withoutTrailingDigits(parts[last] ?? "");
126
+ return SECRET_WORDS.has(word) ? word : undefined;
127
+ }
128
+ /** Where the name ends a token: its last run of name characters (a URL path or escape before it is not the name). */
129
+ export function nameStart(text, start, end) {
130
+ let i = end;
131
+ while (i > start && isNameChar(text.charCodeAt(i - 1)))
132
+ i--;
133
+ return i;
134
+ }
135
+ export function lowerToken(text, start, end, maxLength) {
136
+ return end - start > maxLength ? undefined : text.slice(start, end).toLowerCase();
137
+ }
138
+ export function isInert(text, start, end) {
139
+ const word = lowerToken(text, start, end, 9);
140
+ return word !== undefined && INERT_VALUES.has(word);
141
+ }
142
+ /** Credential-shaped tokens recognized by their prefix wherever a word starts with it. */
143
+ const PREFIX_RULES = [
144
+ { prefixes: ["sk-"], min: 8, chars: isBase64Url },
145
+ { prefixes: ["sk_live_", "sk_test_", "rk_live_", "rk_test_"], min: 16, chars: isAlnum },
146
+ { prefixes: ["gsk_", "xai-", "hf_"], min: 20, chars: isAlnum },
147
+ { prefixes: ["xoxa-", "xoxb-", "xoxo-", "xoxp-", "xoxr-", "xoxs-"], min: 10, chars: isSlackChar },
148
+ { prefixes: ["ghp_", "gho_", "ghu_", "ghs_", "ghr_"], min: 20, chars: isAlnum },
149
+ { prefixes: ["github_pat_"], min: 20, chars: isPatChar },
150
+ { prefixes: ["AKIA", "ASIA"], min: 16, chars: isUpperOrDigit, exact: true },
151
+ { prefixes: ["AIza"], min: 30, chars: isBase64Url },
152
+ ];
153
+ /** Bounded work: the prefix plus at most min + 1 characters after it. */
154
+ function matchesPrefixRule(text, start, end, rule) {
155
+ for (const prefix of rule.prefixes) {
156
+ if (!matchesAt(text, start, prefix, end))
157
+ continue;
158
+ let run = 0;
159
+ const limit = rule.exact === true ? rule.min + 1 : rule.min;
160
+ for (let i = start + prefix.length; i < end && run < limit && rule.chars(text.charCodeAt(i)); i++)
161
+ run++;
162
+ return rule.exact === true ? run === rule.min : run >= rule.min;
163
+ }
164
+ return false;
165
+ }
166
+ /** A known key ends at its alphabet's boundary, not at the end of a URL or filename containing it. */
167
+ export function credentialShapeEnd(text, start, end) {
168
+ for (const rule of PREFIX_RULES) {
169
+ if (!matchesPrefixRule(text, start, end, rule))
170
+ continue;
171
+ for (const prefix of rule.prefixes) {
172
+ if (!matchesAt(text, start, prefix, end))
173
+ continue;
174
+ let i = start + prefix.length;
175
+ while (i < end) {
176
+ const unit = readUnit(text, i);
177
+ if (!rule.chars(unit.character))
178
+ break;
179
+ i += unit.length;
180
+ }
181
+ return i;
182
+ }
183
+ }
184
+ // JWTs have their own segment grammar; retain the conservative span for those.
185
+ return end;
186
+ }
187
+ /**
188
+ * Whether a JWT (three base64url segments joined by ".", the first a JSON header "eyJ") starts at p: -1 when one does,
189
+ * else where the next JWT candidate may start. A candidate before the first "." after p sees the same segments with a
190
+ * shorter first one, so it cannot match either; skipping to that "." caps every walk at three segments, and each
191
+ * segment is walked by at most three candidates.
192
+ */
193
+ function jwtAt(text, p, end) {
194
+ if (!matchesAt(text, p, "eyJ", end))
195
+ return p + 1;
196
+ let segments = 1;
197
+ let length = 0;
198
+ let firstDot = -1;
199
+ let i = p;
200
+ for (; i < end;) {
201
+ const unit = readUnit(text, i);
202
+ const c = unit.character;
203
+ if (c === 46) {
204
+ if (firstDot < 0)
205
+ firstDot = i;
206
+ const next = i + unit.length < end ? readUnit(text, i + unit.length).character : 0;
207
+ if (segments === 3)
208
+ return length >= 5 && !isBase64Url(next) ? -1 : firstDot;
209
+ if (length < (segments === 1 ? 8 : 5))
210
+ return firstDot;
211
+ segments++;
212
+ length = 0;
213
+ }
214
+ else if (isBase64Url(c))
215
+ length++;
216
+ else
217
+ break;
218
+ i += unit.length;
219
+ }
220
+ if (segments === 3 && length >= 5)
221
+ return -1;
222
+ return firstDot >= 0 ? firstDot : i;
223
+ }
224
+ /**
225
+ * Where the first credential-shaped run in [start, end) begins, or -1: a known key prefix or a JWT at the start of a
226
+ * word anywhere in the token (after "/", "+", "~", "-", "." or "%", so URL userinfo, paths and joined values count),
227
+ * including percent-encoded token punctuation, never inside a word ("tasks-..." is not "sk-...").
228
+ * After a credential has already been found, withinCredential also checks inside words: an adjoining key may have
229
+ * had its prefix consumed by the first key's alphabet. The caller retains the whole token in that ambiguous case.
230
+ * Constant work per character plus the JWT walks bounded above.
231
+ */
232
+ export function credentialShapeStart(text, start, end, withinCredential = false) {
233
+ let stop = end;
234
+ while (stop > start && text.charCodeAt(stop - 1) === 46)
235
+ stop--;
236
+ let jwtFrom = start;
237
+ let wordStart = true;
238
+ for (let p = start; p < stop;) {
239
+ const unit = readUnit(text, p);
240
+ const candidate = withinCredential || wordStart;
241
+ wordStart = !isWordChar(unit.character);
242
+ const position = p;
243
+ p += unit.length;
244
+ if (!candidate)
245
+ continue;
246
+ for (const rule of PREFIX_RULES)
247
+ if (matchesPrefixRule(text, position, stop, rule))
248
+ return position;
249
+ if (position < jwtFrom)
250
+ continue;
251
+ const next = jwtAt(text, position, stop);
252
+ if (next < 0)
253
+ return position;
254
+ jwtFrom = next;
255
+ }
256
+ return -1;
257
+ }
258
+ //# sourceMappingURL=secret-classifier.js.map
@@ -0,0 +1,51 @@
1
+ interface Unit {
2
+ character: number;
3
+ length: number;
4
+ refusal?: "percent-window";
5
+ }
6
+ export declare const isDigit: (c: number) => boolean;
7
+ export declare const isUpper: (c: number) => boolean;
8
+ export declare const isLower: (c: number) => boolean;
9
+ export declare const isAlnum: (c: number) => boolean;
10
+ export declare const isBase64Url: (c: number) => boolean;
11
+ export declare const isNameChar: (c: number) => boolean;
12
+ export declare function matchesAt(text: string, i: number, literal: string, limit?: number): boolean;
13
+ /** The length of the token unit at i (a value character, or a percent escape of one), or 0 at a delimiter. */
14
+ export declare function tokenUnitLength(text: string, i: number): number;
15
+ /** One logical character and its full source width, shared by token, delimiter and word-boundary scans. */
16
+ export declare function readUnit(text: string, i: number): Unit;
17
+ export interface Delimiter {
18
+ end: number;
19
+ /** The first unresolved percent window; callers conservatively redact from here to the end. */
20
+ refusalStart: number;
21
+ /** One ":" or "=" between quotes, escapes and spaces, then an optional "[" or "{". */
22
+ separator: boolean;
23
+ opensObject: boolean;
24
+ braceDepth: number;
25
+ /** The end of the "=" padding it starts with, which belongs to the token before it. */
26
+ paddingEnd: number;
27
+ pairEquals: boolean;
28
+ cookieGap: boolean;
29
+ spaceOnly: boolean;
30
+ endsWithLt: boolean;
31
+ closesTag: boolean;
32
+ colonOnly: boolean;
33
+ at: number;
34
+ endsUserinfo: boolean;
35
+ /** The quote character the delimiter ends with (it opens the value after it), or -1. */
36
+ quote: number;
37
+ /** How many backslashes escape that quote: the escaping depth its closing quote is written at. */
38
+ quoteBackslashes: number;
39
+ }
40
+ export declare function emptyDelimiter(): Delimiter;
41
+ /** Read the delimiter that starts at i into d, one unit at a time. */
42
+ export declare function readDelimiter(text: string, i: number, d: Delimiter): void;
43
+ /**
44
+ * Where a quoted value that continues at i ends: its closing quote, written at the opening quote's escaping depth (a
45
+ * quote behind more backslashes is escaped inside the value), or the end of the line.
46
+ */
47
+ export declare function quotedValueEnd(text: string, i: number, quote: number, depth: number): number;
48
+ /** Where a bare value that continues at i ends: whitespace, ",", ";", "&", a quote or "<" (a closing tag). */
49
+ export declare function bareValueEnd(text: string, i: number): number;
50
+ export {};
51
+ //# sourceMappingURL=secret-lexer.d.ts.map
@@ -0,0 +1,295 @@
1
+ // The lexical layer of the credential redactor (senpi#4283): character classes, percent escapes and HTML entities read
2
+ // as what they encode, the token and delimiter units the one-pass scanner alternates between, and the forward scans
3
+ // that find where a quoted or bare value ends. Every function reads forward from the index it is given; none searches
4
+ // back or uses a regular expression.
5
+ const HTML_ENTITIES = [
6
+ ["&quot;", 34],
7
+ ["&#34;", 34],
8
+ ["&#034;", 34],
9
+ ["&#x22;", 34],
10
+ ["&#39;", 39],
11
+ ["&#039;", 39],
12
+ ["&#x27;", 39],
13
+ ["&lt;", 60],
14
+ ["&gt;", 62],
15
+ ["&amp;", 38],
16
+ ];
17
+ const MAX_SEPARATOR_LENGTH = 32;
18
+ const MAX_SCHEME_GAP = 8;
19
+ const PERCENT_WINDOW = 64;
20
+ export const isDigit = (c) => c >= 48 && c <= 57;
21
+ export const isUpper = (c) => c >= 65 && c <= 90;
22
+ export const isLower = (c) => c >= 97 && c <= 122;
23
+ export const isAlnum = (c) => isDigit(c) || isUpper(c) || isLower(c);
24
+ export const isBase64Url = (c) => isAlnum(c) || c === 95 || c === 45;
25
+ export const isNameChar = (c) => isBase64Url(c) || c === 46;
26
+ const isTokenChar = (c) => isNameChar(c) || c === 43 || c === 47 || c === 126;
27
+ const isSpace = (c) => c === 32 || c === 9 || c === 10 || c === 13;
28
+ const isQuote = (c) => c === 34 || c === 39 || c === 96 || c === 92;
29
+ const isOpeningQuote = (c) => c === 34 || c === 39 || c === 96;
30
+ const isTypographicQuote = (c) => c === 0x201c || c === 0x201d || c === 0x2018 || c === 0x2019;
31
+ function hexValue(c) {
32
+ if (isDigit(c))
33
+ return c - 48;
34
+ if (c >= 65 && c <= 70)
35
+ return c - 55;
36
+ if (c >= 97 && c <= 102)
37
+ return c - 87;
38
+ return -1;
39
+ }
40
+ export function matchesAt(text, i, literal, limit = text.length) {
41
+ if (i + literal.length > limit)
42
+ return false;
43
+ for (let k = 0; k < literal.length; k++)
44
+ if (text.charCodeAt(i + k) !== literal.charCodeAt(k))
45
+ return false;
46
+ return true;
47
+ }
48
+ /** The length of the token unit at i (a value character, or a percent escape of one), or 0 at a delimiter. */
49
+ export function tokenUnitLength(text, i) {
50
+ const c = text.charCodeAt(i);
51
+ if (isTokenChar(c))
52
+ return 1;
53
+ if (c !== 37)
54
+ return 0;
55
+ const unit = readUnit(text, i);
56
+ if (unit.refusal !== undefined)
57
+ return 0;
58
+ return isTokenChar(unit.character) || unit.length === 1 ? unit.length : 0;
59
+ }
60
+ /**
61
+ * Repeated full-window percent decoding to a fixpoint, keeping each character's original source end.
62
+ * A pass shrinks the 64-character window by at least two, so both work and storage here are bounded.
63
+ * Malformed escapes are literal, but an unfinished escape at the window edge is not a proven boundary.
64
+ */
65
+ function percentWindow(text, start, prefixEnd) {
66
+ const limit = Math.min(text.length, prefixEnd + PERCENT_WINDOW - 1);
67
+ let needsDecoding = false;
68
+ let contiguous = true;
69
+ let previous = 37;
70
+ for (let p = prefixEnd; p < limit; p++) {
71
+ const c = text.charCodeAt(p);
72
+ const hex = hexValue(c) >= 0;
73
+ if (previous === 37 && hex) {
74
+ needsDecoding = true;
75
+ break;
76
+ }
77
+ if (c !== 37 && !hex)
78
+ contiguous = false;
79
+ previous = c;
80
+ }
81
+ // Without a possible escape no pass can change the window, so do not allocate its fixpoint buffers.
82
+ if (!needsDecoding) {
83
+ if (contiguous && limit < text.length)
84
+ return { character: 37, length: limit - start, refusal: "percent-window" };
85
+ return { character: 37, length: prefixEnd - start };
86
+ }
87
+ const characters = [37];
88
+ const ends = [prefixEnd];
89
+ for (let p = prefixEnd; p < limit; p++) {
90
+ characters.push(text.charCodeAt(p));
91
+ ends.push(p + 1);
92
+ }
93
+ let changed;
94
+ do {
95
+ changed = false;
96
+ let written = 0;
97
+ for (let p = 0; p < characters.length; p++) {
98
+ let c = characters[p] ?? 0;
99
+ const high = hexValue(characters[p + 1] ?? -1);
100
+ const low = hexValue(characters[p + 2] ?? -1);
101
+ if (c === 37 && high >= 0 && low >= 0) {
102
+ c = high * 16 + low;
103
+ p += 2;
104
+ changed = true;
105
+ }
106
+ characters[written] = c;
107
+ ends[written++] = ends[p] ?? prefixEnd;
108
+ }
109
+ characters.length = written;
110
+ ends.length = written;
111
+ } while (changed);
112
+ const character = characters[0] ?? 37;
113
+ if (character === 37 && limit < text.length) {
114
+ // Only a contiguous percent/hex run can depend on text beyond the window. A non-hex literal
115
+ // breaks the dependency: the percent before it can never decode on any later pass.
116
+ for (let p = 1; p < characters.length; p++) {
117
+ const c = characters[p] ?? 0;
118
+ if (c !== 37 && hexValue(c) < 0)
119
+ return { character, length: (ends[0] ?? prefixEnd) - start };
120
+ }
121
+ return { character, length: limit - start, refusal: "percent-window" };
122
+ }
123
+ return { character, length: (ends[0] ?? prefixEnd) - start };
124
+ }
125
+ /** One logical character and its full source width, shared by token, delimiter and word-boundary scans. */
126
+ export function readUnit(text, i) {
127
+ const c = text.charCodeAt(i);
128
+ if (c === 37) {
129
+ let end = i + 1;
130
+ let decoded = c;
131
+ // Collapse literal %25 layers in one forward walk before the bounded full-window decoder.
132
+ // This is the fixpoint of that prefix, not a depth cap: even million-layer canonical chains stay linear.
133
+ while (end + 1 < text.length) {
134
+ const high = hexValue(text.charCodeAt(end));
135
+ const low = hexValue(text.charCodeAt(end + 1));
136
+ if (high < 0 || low < 0)
137
+ break;
138
+ decoded = high * 16 + low;
139
+ end += 2;
140
+ if (decoded !== 37)
141
+ return { character: decoded, length: end - i };
142
+ }
143
+ return percentWindow(text, i, end);
144
+ }
145
+ if (c === 38) {
146
+ for (const [entity, character] of HTML_ENTITIES) {
147
+ if (matchesAt(text, i, entity))
148
+ return { character, length: entity.length };
149
+ }
150
+ }
151
+ if (c === 0xff1a)
152
+ return { character: 58, length: 1 };
153
+ if (c === 0x201c || c === 0x201d)
154
+ return { character: 34, length: 1 };
155
+ if (c === 0x2018 || c === 0x2019)
156
+ return { character: 39, length: 1 };
157
+ return { character: c, length: 1 };
158
+ }
159
+ export function emptyDelimiter() {
160
+ return {
161
+ end: 0,
162
+ refusalStart: -1,
163
+ separator: false,
164
+ opensObject: false,
165
+ braceDepth: 0,
166
+ paddingEnd: 0,
167
+ pairEquals: false,
168
+ cookieGap: false,
169
+ spaceOnly: false,
170
+ endsWithLt: false,
171
+ closesTag: false,
172
+ colonOnly: false,
173
+ at: -1,
174
+ endsUserinfo: false,
175
+ quote: -1,
176
+ quoteBackslashes: 0,
177
+ };
178
+ }
179
+ /** Read the delimiter that starts at i into d, one unit at a time. */
180
+ export function readDelimiter(text, i, d) {
181
+ let position = i;
182
+ let units = 0;
183
+ let separators = 0;
184
+ let separatorShape = true;
185
+ let gapMarks = 0;
186
+ let gapShape = true;
187
+ let padding = true;
188
+ let spaces = 0;
189
+ let backslashes = 0;
190
+ let last = -1;
191
+ let first = -1;
192
+ let tagShape = true;
193
+ d.opensObject = false;
194
+ d.braceDepth = 0;
195
+ d.paddingEnd = i;
196
+ d.at = -1;
197
+ d.endsUserinfo = false;
198
+ d.quote = -1;
199
+ d.quoteBackslashes = 0;
200
+ d.refusalStart = -1;
201
+ while (position < text.length && tokenUnitLength(text, position) === 0) {
202
+ const unit = readUnit(text, position);
203
+ if (unit.refusal !== undefined) {
204
+ d.refusalStart = position;
205
+ position = text.length;
206
+ break;
207
+ }
208
+ const c = unit.character;
209
+ if (units === 0)
210
+ first = c;
211
+ else if (!isSpace(c))
212
+ tagShape = false;
213
+ if (padding && c === 61)
214
+ d.paddingEnd = position + unit.length;
215
+ else {
216
+ padding = false;
217
+ if (c === 59 || c === 44)
218
+ gapMarks++;
219
+ else if (!isSpace(c) && !isQuote(c))
220
+ gapShape = false;
221
+ }
222
+ if (c === 58 || c === 61)
223
+ separators++;
224
+ else if (c === 123) {
225
+ d.braceDepth++;
226
+ if (separators === 1)
227
+ d.opensObject = true;
228
+ else
229
+ separatorShape = false;
230
+ }
231
+ else if (c === 125) {
232
+ d.braceDepth--;
233
+ separatorShape = false;
234
+ }
235
+ else if (!isSpace(c) && !isQuote(c) && !(c === 91 && separators === 1))
236
+ separatorShape = false;
237
+ if (isSpace(c))
238
+ spaces++;
239
+ if (c === 64 && d.at < 0)
240
+ d.at = position;
241
+ if (isSpace(c) || c === 63 || c === 35)
242
+ d.endsUserinfo = true;
243
+ d.quote = isOpeningQuote(c) ? c : -1;
244
+ d.quoteBackslashes = backslashes;
245
+ backslashes = c === 92 ? backslashes + 1 : 0;
246
+ last = c;
247
+ units++;
248
+ position += unit.length;
249
+ }
250
+ d.end = position;
251
+ d.separator = separators === 1 && separatorShape && units <= MAX_SEPARATOR_LENGTH;
252
+ d.pairEquals = units === 1 && first === 61;
253
+ d.cookieGap = gapMarks === 1 && gapShape && units <= MAX_SEPARATOR_LENGTH;
254
+ d.spaceOnly = units > 0 && spaces === units && units <= MAX_SCHEME_GAP;
255
+ d.endsWithLt = last === 60;
256
+ d.closesTag = first === 62 && tagShape;
257
+ d.colonOnly = units === 1 && first === 58;
258
+ }
259
+ /**
260
+ * Where a quoted value that continues at i ends: its closing quote, written at the opening quote's escaping depth (a
261
+ * quote behind more backslashes is escaped inside the value), or the end of the line.
262
+ */
263
+ export function quotedValueEnd(text, i, quote, depth) {
264
+ let position = i;
265
+ let backslashes = 0;
266
+ while (position < text.length) {
267
+ const raw = text.charCodeAt(position);
268
+ if (raw === 10 || raw === 13)
269
+ return position;
270
+ if (raw === 92) {
271
+ backslashes++;
272
+ position++;
273
+ continue;
274
+ }
275
+ const unit = readUnit(text, position);
276
+ if (unit.refusal !== undefined)
277
+ return text.length;
278
+ if (unit.character === quote && backslashes <= depth)
279
+ return position - backslashes;
280
+ backslashes = 0;
281
+ position += unit.length;
282
+ }
283
+ return position;
284
+ }
285
+ /** Where a bare value that continues at i ends: whitespace, ",", ";", "&", a quote or "<" (a closing tag). */
286
+ export function bareValueEnd(text, i) {
287
+ let position = i;
288
+ for (; position < text.length; position++) {
289
+ const c = text.charCodeAt(position);
290
+ if (isSpace(c) || isQuote(c) || isTypographicQuote(c) || c === 44 || c === 59 || c === 38 || c === 60)
291
+ break;
292
+ }
293
+ return position;
294
+ }
295
+ //# sourceMappingURL=secret-lexer.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Mask credentials in provider or gateway text by value, not by a list of names: the value after a name that ends
3
+ * with a credential word (in a header, JSON, array, Go map, Python repr, XML, URL query, form body, cookie or
4
+ * environment assignment, with ASCII, typographic or HTML-escaped quotes, at any escaping depth; 16 characters or
5
+ * more, or the whole value at any length under a password, secret, authorization or bearer name), every cookie value,
6
+ * every long value of an object held under such a name, URL userinfo passwords, bearer tokens, and known key prefixes
7
+ * and JWTs wherever a word starts with one (inside URLs, userinfo and paths too). Model ids, error codes, request ids
8
+ * and names that merely contain a credential word stay readable. One linear pass, no regular expressions.
9
+ */
10
+ export declare function redactSecrets(text: string, replacement?: string): string;
11
+ /** Mask only credential-shaped runs (known key prefixes and JWTs), whatever surrounds them. One linear pass. */
12
+ export declare function redactCredentialShapes(text: string, replacement?: string): string;
13
+ //# sourceMappingURL=secret-redaction.d.ts.map