@sema-agent/client-core 0.82.7 → 0.83.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +97 -0
- package/README.md +15 -7
- package/dist/adapt/arms.d.ts +3 -2
- package/dist/adapt/arms.js +17 -11
- package/dist/adapt/ids.js +12 -8
- package/dist/adapt/textStream.d.ts +3 -2
- package/dist/adapt/toolCards.d.ts +2 -3
- package/dist/adapt/toolCards.js +1 -1
- package/dist/adapt.d.ts +1 -1
- package/dist/adapt.js +13 -6
- package/dist/adapter/activeRunSelfHeal.js +34 -17
- package/dist/adapter/downstream/eventToSdkMessage.js +1 -1
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +2 -4
- package/dist/adapter/downstream/terminalToSdkResult.js +46 -8
- package/dist/adapter/runStream.js +25 -8
- package/dist/clientSlice.d.ts +0 -5
- package/dist/displayUntrusted.d.ts +24 -0
- package/dist/displayUntrusted.js +1437 -0
- package/dist/engineNoticeCodes.js +4 -0
- package/dist/fleet/fleetRowAgentType.d.ts +0 -1
- package/dist/fleet/fleetRowAgentType.js +0 -3
- package/dist/fleetTaskDesc.js +3 -3
- package/dist/gateVocabulary.js +1 -1
- package/dist/hitl/approvalDecisionNoteAudit.js +4 -2
- package/dist/hitl/approvalResolution.js +2 -0
- package/dist/hitl/frameRouter.d.ts +3 -2
- package/dist/hitl/sessionPolicyDeliverable.d.ts +14 -0
- package/dist/hitl/sessionPolicyDeliverable.js +80 -0
- package/dist/hitl/toolApprovalWire.d.ts +1 -0
- package/dist/hitl/toolApprovalWire.js +56 -10
- package/dist/hooksWireCaps.d.ts +6 -1
- package/dist/hooksWireCaps.js +220 -13
- package/dist/host.d.ts +25 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/liveInitToolFace.d.ts +4 -2
- package/dist/liveInitToolFace.js +2 -2
- package/dist/model/catalogLoader.d.ts +1 -6
- package/dist/panelRunningHistory.d.ts +3 -2
- package/dist/peerFrames.d.ts +0 -1
- package/dist/peerFrames.js +2 -1
- package/dist/pluginHooksWire.d.ts +92 -0
- package/dist/pluginHooksWire.js +429 -0
- package/dist/printInitToolFace.js +1 -1
- package/dist/request/printNotification.js +26 -26
- package/dist/seam.d.ts +8 -2
- package/dist/seam.js +18 -7
- package/dist/subagent/engineSubagentResume.js +2 -1
- package/dist/systemReminderTag.d.ts +0 -1
- package/dist/systemReminderTag.js +0 -3
- package/dist/toolResult.js +1 -0
- package/dist/uuidV5.d.ts +3 -0
- package/dist/uuidV5.js +178 -0
- package/dist/webSearchWireCaps.d.ts +0 -3
- package/dist/webSearchWireCaps.js +21 -51
- package/dist/wireFailureShape.d.ts +2 -1
- package/docs/INTEGRATION-CLIENTS.md +794 -13
- package/package.json +2 -2
|
@@ -0,0 +1,1437 @@
|
|
|
1
|
+
export const REDACTION_MARKER_PREFIX = '\u00abredacted';
|
|
2
|
+
const MARKER_RE = /^\u00abredacted(?::[a-z-]{1,16}){0,2}\u00bb$/;
|
|
3
|
+
const REDACTED_USERINFO = '\u00abredacted:userinfo\u00bb';
|
|
4
|
+
const REDACTED_QUERY = '\u00abredacted:query\u00bb';
|
|
5
|
+
const REDACTED_FRAGMENT = '\u00abredacted:fragment\u00bb';
|
|
6
|
+
const REDACTED_SECRET = '\u00abredacted:secret\u00bb';
|
|
7
|
+
const RETIRED_MARKER = '***';
|
|
8
|
+
function isMarker(v) {
|
|
9
|
+
const core = v.includes(VIEW_SEPARATOR) ? v.split(VIEW_SEPARATOR).join('') : v;
|
|
10
|
+
return core === RETIRED_MARKER || MARKER_RE.test(core);
|
|
11
|
+
}
|
|
12
|
+
const VIEW_SEPARATOR = '\ufeff';
|
|
13
|
+
function pushEdit(edits, src, start, end, text) {
|
|
14
|
+
let a = start;
|
|
15
|
+
let b = end;
|
|
16
|
+
while (a < b && src[a] === VIEW_SEPARATOR)
|
|
17
|
+
a++;
|
|
18
|
+
while (b > a && src[b - 1] === VIEW_SEPARATOR)
|
|
19
|
+
b--;
|
|
20
|
+
if (a === b && b !== end)
|
|
21
|
+
a = b = start;
|
|
22
|
+
if (src.slice(a, b) === text)
|
|
23
|
+
return;
|
|
24
|
+
edits.push({ start: a, end: b, text });
|
|
25
|
+
}
|
|
26
|
+
function applyEdits(text, edits) {
|
|
27
|
+
if (edits.length === 0)
|
|
28
|
+
return text;
|
|
29
|
+
const parts = [];
|
|
30
|
+
let cursor = 0;
|
|
31
|
+
for (const e of edits) {
|
|
32
|
+
parts.push(text.slice(cursor, e.start), e.text);
|
|
33
|
+
cursor = e.end;
|
|
34
|
+
}
|
|
35
|
+
parts.push(text.slice(cursor));
|
|
36
|
+
return parts.join('');
|
|
37
|
+
}
|
|
38
|
+
class PlainTarget {
|
|
39
|
+
text;
|
|
40
|
+
constructor(text) {
|
|
41
|
+
this.text = text;
|
|
42
|
+
}
|
|
43
|
+
apply(edits) {
|
|
44
|
+
this.text = applyEdits(this.text, edits);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
class TrackedTarget {
|
|
48
|
+
text;
|
|
49
|
+
src;
|
|
50
|
+
spans = [];
|
|
51
|
+
viewLength;
|
|
52
|
+
constructor(text, viewLength) {
|
|
53
|
+
this.text = text;
|
|
54
|
+
this.viewLength = viewLength;
|
|
55
|
+
this.src = new Int32Array(text.length);
|
|
56
|
+
for (let k = 0; k < text.length; k++)
|
|
57
|
+
this.src[k] = k;
|
|
58
|
+
}
|
|
59
|
+
rangeStart(k) {
|
|
60
|
+
const s = this.src[k] ?? 0;
|
|
61
|
+
return s >= 0 ? s : (this.spans[2 * (-1 - s)] ?? 0);
|
|
62
|
+
}
|
|
63
|
+
rangeEnd(k) {
|
|
64
|
+
const s = this.src[k] ?? 0;
|
|
65
|
+
return s >= 0 ? s + 1 : (this.spans[2 * (-1 - s) + 1] ?? 0);
|
|
66
|
+
}
|
|
67
|
+
apply(edits) {
|
|
68
|
+
if (edits.length === 0)
|
|
69
|
+
return;
|
|
70
|
+
let length = this.text.length;
|
|
71
|
+
for (const e of edits)
|
|
72
|
+
length += e.text.length - (e.end - e.start);
|
|
73
|
+
const src = new Int32Array(length);
|
|
74
|
+
const parts = [];
|
|
75
|
+
let cursor = 0;
|
|
76
|
+
let w = 0;
|
|
77
|
+
for (const e of edits) {
|
|
78
|
+
src.set(this.src.subarray(cursor, e.start), w);
|
|
79
|
+
w += e.start - cursor;
|
|
80
|
+
parts.push(this.text.slice(cursor, e.start), e.text);
|
|
81
|
+
let a;
|
|
82
|
+
let b;
|
|
83
|
+
if (e.start === e.end) {
|
|
84
|
+
a = e.start < this.text.length ? this.rangeStart(e.start) : this.viewLength;
|
|
85
|
+
b = a;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
a = this.rangeStart(e.start);
|
|
89
|
+
b = this.rangeEnd(e.start);
|
|
90
|
+
for (let k = e.start + 1; k < e.end; k++) {
|
|
91
|
+
a = Math.min(a, this.rangeStart(k));
|
|
92
|
+
b = Math.max(b, this.rangeEnd(k));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const id = this.spans.length / 2;
|
|
96
|
+
this.spans.push(a, b);
|
|
97
|
+
src.fill(-1 - id, w, w + e.text.length);
|
|
98
|
+
w += e.text.length;
|
|
99
|
+
cursor = e.end;
|
|
100
|
+
}
|
|
101
|
+
src.set(this.src.subarray(cursor), w);
|
|
102
|
+
parts.push(this.text.slice(cursor));
|
|
103
|
+
this.text = parts.join('');
|
|
104
|
+
this.src = src;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const SECRET_LITERAL_RE = /^(sk-|gh[opus]_|github_pat_|xox[baprse]-|xapp-|bearer\s|basic\s|eyJ[A-Za-z0-9_-]{10,}\.|AKIA[0-9A-Z]{12,}|AIza[0-9A-Za-z_-]{20,}|-----BEGIN[A-Z0-9 ]*PRIVATE KEY)/i;
|
|
108
|
+
const SCHEME_TOKEN_RE = /(?:^|[^A-Za-z0-9_-])(?:bearer|basic)[ \t]+\S{8,4096}/i;
|
|
109
|
+
const JWT_RE = /(?:^|[^A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{8,2048}\.[A-Za-z0-9_-]{8,2048}\.[A-Za-z0-9_-]{4,2048}/i;
|
|
110
|
+
function scanForSecretLiteral(s) {
|
|
111
|
+
if (SECRET_LITERAL_RE.test(s) || SCHEME_TOKEN_RE.test(s) || JWT_RE.test(s))
|
|
112
|
+
return true;
|
|
113
|
+
return s.split(/[^A-Za-z0-9_-]+/).some(tok => tok !== '' && SECRET_LITERAL_RE.test(tok));
|
|
114
|
+
}
|
|
115
|
+
function decodeAsciiEscapes(s) {
|
|
116
|
+
return s.replace(/%[0-9A-Fa-f]{2}/g, esc => {
|
|
117
|
+
const code = Number.parseInt(esc.slice(1), 16);
|
|
118
|
+
return code < 0x80 ? String.fromCharCode(code) : esc;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
export function looksLikeKnownSecret(s) {
|
|
122
|
+
let cur = s;
|
|
123
|
+
for (let layer = 0; layer < 4; layer++) {
|
|
124
|
+
if (scanForSecretLiteral(cur))
|
|
125
|
+
return true;
|
|
126
|
+
const next = decodeAsciiEscapes(cur);
|
|
127
|
+
if (next === cur)
|
|
128
|
+
break;
|
|
129
|
+
cur = next;
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
const TOKEN_STOP = new Set(['"', '<', '>', '`', '|', '\\', '^', '{', '}']);
|
|
134
|
+
const TRAILING_PUNCTUATION = new Set(['.', ',', ';', ':', '!', '?', "'"]);
|
|
135
|
+
const VALUE_BOUNDARY_TAIL = /[?#&=;]$/;
|
|
136
|
+
const isWhitespaceOrControl = (ch) => {
|
|
137
|
+
const c = ch.charCodeAt(0);
|
|
138
|
+
return c <= 0x20 || c === 0x7f;
|
|
139
|
+
};
|
|
140
|
+
const isSchemeChar = (ch) => (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch === '+' || ch === '.' || ch === '-';
|
|
141
|
+
const isAlpha = (ch) => (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
|
|
142
|
+
const isHexDigit = (ch) => (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
|
|
143
|
+
function isRegexSpace(c) {
|
|
144
|
+
return ((c >= 0x09 && c <= 0x0d) || c === 0x20 || c === 0xa0 || c === 0x1680 || (c >= 0x2000 && c <= 0x200a) ||
|
|
145
|
+
c === 0x2028 || c === 0x2029 || c === 0x202f || c === 0x205f || c === 0x3000 || c === 0xfeff);
|
|
146
|
+
}
|
|
147
|
+
const breaksUserinfo = (c) => c === 0x40 || c === 0x2f || c === 0x3f || c === 0x23 || isRegexSpace(c);
|
|
148
|
+
const blocksUserinfoStart = (c) => (c >= 0x30 && c <= 0x39) || (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) ||
|
|
149
|
+
c === 0x2b || c === 0x2e || c === 0x2d || c === 0x40 || c === 0x3a;
|
|
150
|
+
const isHostChar = (c) => (c >= 0x30 && c <= 0x39) || (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) ||
|
|
151
|
+
c === 0x2d || c === 0x2e || c === 0x5f || c === 0x7e || c === 0x25;
|
|
152
|
+
const isIpv6Char = (c) => (c >= 0x30 && c <= 0x3a) || (c >= 0x41 && c <= 0x46) || (c >= 0x61 && c <= 0x66) || c === 0x2e;
|
|
153
|
+
const endsSchemelessHost = (c) => Number.isNaN(c) || c === 0x2f || c === 0x3f || c === 0x23 || c === 0x22 || c === 0x3c || c === 0x3e || c === 0x60 ||
|
|
154
|
+
c === 0x7c || c === 0x5c || c === 0x5e || c === 0x7b || c === 0x7d || isRegexSpace(c);
|
|
155
|
+
function schemelessHostEnd(text, from) {
|
|
156
|
+
let k = from;
|
|
157
|
+
if (text.charCodeAt(k) === 0x5b) {
|
|
158
|
+
k++;
|
|
159
|
+
while (k < text.length && isIpv6Char(text.charCodeAt(k)))
|
|
160
|
+
k++;
|
|
161
|
+
if (k - from - 1 < 2 || text.charCodeAt(k) !== 0x5d)
|
|
162
|
+
return -1;
|
|
163
|
+
k++;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
while (k < text.length && isHostChar(text.charCodeAt(k)))
|
|
167
|
+
k++;
|
|
168
|
+
if (k === from)
|
|
169
|
+
return -1;
|
|
170
|
+
}
|
|
171
|
+
if (text.charCodeAt(k) === 0x3a) {
|
|
172
|
+
let d = k + 1;
|
|
173
|
+
while (d < text.length && text.charCodeAt(d) >= 0x30 && text.charCodeAt(d) <= 0x39)
|
|
174
|
+
d++;
|
|
175
|
+
if (d > k + 1)
|
|
176
|
+
k = d;
|
|
177
|
+
}
|
|
178
|
+
return endsSchemelessHost(text.charCodeAt(k)) ? k : -1;
|
|
179
|
+
}
|
|
180
|
+
function valueCoreForJudging(value) {
|
|
181
|
+
if (value.length === 0 || isMarker(value))
|
|
182
|
+
return '';
|
|
183
|
+
let end = value.length;
|
|
184
|
+
while (end > 0 && TRAILING_PUNCTUATION.has(value.charAt(end - 1)))
|
|
185
|
+
end--;
|
|
186
|
+
return value.slice(0, end);
|
|
187
|
+
}
|
|
188
|
+
function isKnownSecretSegment(value) {
|
|
189
|
+
const core = valueCoreForJudging(value);
|
|
190
|
+
return core.length > 0 && looksLikeKnownSecret(core);
|
|
191
|
+
}
|
|
192
|
+
function queryEdits(src, base, query, edits) {
|
|
193
|
+
let pos = base;
|
|
194
|
+
for (const part of query.split('&')) {
|
|
195
|
+
if (part.length > 0) {
|
|
196
|
+
const eq = part.indexOf('=');
|
|
197
|
+
if (eq === -1) {
|
|
198
|
+
if (!isMarker(part))
|
|
199
|
+
pushEdit(edits, src, pos, pos + part.length, REDACTED_QUERY);
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
const value = part.slice(eq + 1);
|
|
203
|
+
if (value.length > 0 && !isMarker(value))
|
|
204
|
+
pushEdit(edits, src, pos + eq + 1, pos + part.length, REDACTED_QUERY);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
pos += part.length + 1;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function pathEdits(src, base, path, opaqueSegments, edits) {
|
|
211
|
+
let pos = base;
|
|
212
|
+
for (const segment of path.split('/')) {
|
|
213
|
+
const semi = segment.indexOf(';');
|
|
214
|
+
const head = semi === -1 ? segment : segment.slice(0, semi);
|
|
215
|
+
if (opaqueSegments && isKnownSecretSegment(head))
|
|
216
|
+
pushEdit(edits, src, pos, pos + head.length, REDACTED_SECRET);
|
|
217
|
+
if (semi !== -1) {
|
|
218
|
+
let ppos = pos + semi + 1;
|
|
219
|
+
for (const part of segment.slice(semi + 1).split(';')) {
|
|
220
|
+
const eq = part.indexOf('=');
|
|
221
|
+
if (eq !== -1) {
|
|
222
|
+
const value = part.slice(eq + 1);
|
|
223
|
+
if (value.length > 0 && !isMarker(value))
|
|
224
|
+
pushEdit(edits, src, ppos + eq + 1, ppos + part.length, REDACTED_QUERY);
|
|
225
|
+
}
|
|
226
|
+
ppos += part.length + 1;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
pos += segment.length + 1;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function urlTokenEditsAt(src, start, end, edits) {
|
|
233
|
+
const token = src.slice(start, end);
|
|
234
|
+
const schemeEnd = token.indexOf('://') + '://'.length;
|
|
235
|
+
const scheme = token.slice(0, schemeEnd);
|
|
236
|
+
const rest = token.slice(schemeEnd);
|
|
237
|
+
const authorityEnd = rest.search(/[/?#]/);
|
|
238
|
+
const authority = authorityEnd === -1 ? rest : rest.slice(0, authorityEnd);
|
|
239
|
+
const tailStart = schemeEnd + authority.length;
|
|
240
|
+
const tail = token.slice(tailStart);
|
|
241
|
+
const at = authority.lastIndexOf('@');
|
|
242
|
+
if (at !== -1)
|
|
243
|
+
pushEdit(edits, src, start + schemeEnd, start + schemeEnd + at, REDACTED_USERINFO);
|
|
244
|
+
const hash = tail.indexOf('#');
|
|
245
|
+
const beforeHash = hash === -1 ? tail : tail.slice(0, hash);
|
|
246
|
+
const question = beforeHash.indexOf('?');
|
|
247
|
+
const path = question === -1 ? beforeHash : beforeHash.slice(0, question);
|
|
248
|
+
pathEdits(src, start + tailStart, path, !/^file:/i.test(scheme), edits);
|
|
249
|
+
if (question !== -1)
|
|
250
|
+
queryEdits(src, start + tailStart + question + 1, beforeHash.slice(question + 1), edits);
|
|
251
|
+
if (hash !== -1) {
|
|
252
|
+
const rawFragment = tail.slice(hash + 1);
|
|
253
|
+
if (rawFragment.length > 0 && !isMarker(rawFragment))
|
|
254
|
+
pushEdit(edits, src, start + tailStart + hash + 1, end, REDACTED_FRAGMENT);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function urlTokenEdits(text) {
|
|
258
|
+
const edits = [];
|
|
259
|
+
let mark = text.indexOf('://');
|
|
260
|
+
while (mark !== -1) {
|
|
261
|
+
let start = mark;
|
|
262
|
+
while (start > 0 && isSchemeChar(text.charAt(start - 1)))
|
|
263
|
+
start--;
|
|
264
|
+
while (start < mark && !isAlpha(text.charAt(start)))
|
|
265
|
+
start++;
|
|
266
|
+
if (start === mark) {
|
|
267
|
+
mark = text.indexOf('://', mark + 3);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
let end = mark + 3;
|
|
271
|
+
while (end < text.length) {
|
|
272
|
+
const ch = text.charAt(end);
|
|
273
|
+
if (isWhitespaceOrControl(ch) || TOKEN_STOP.has(ch))
|
|
274
|
+
break;
|
|
275
|
+
if (ch === VIEW_SEPARATOR) {
|
|
276
|
+
let p = end;
|
|
277
|
+
while (p < text.length && text[p] === VIEW_SEPARATOR)
|
|
278
|
+
p++;
|
|
279
|
+
if (credentialWordStartsNear(text, p))
|
|
280
|
+
break;
|
|
281
|
+
end = p;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
end++;
|
|
285
|
+
}
|
|
286
|
+
while (end > mark + 3 && text.charAt(end - 1) === VIEW_SEPARATOR)
|
|
287
|
+
end--;
|
|
288
|
+
let runStart = end;
|
|
289
|
+
while (runStart > mark + 3 && TRAILING_PUNCTUATION.has(text.charAt(runStart - 1)))
|
|
290
|
+
runStart--;
|
|
291
|
+
if (runStart < end && !VALUE_BOUNDARY_TAIL.test(text.slice(start, runStart)))
|
|
292
|
+
end = runStart;
|
|
293
|
+
urlTokenEditsAt(text, start, end, edits);
|
|
294
|
+
mark = text.indexOf('://', end > mark + 3 ? end : mark + 3);
|
|
295
|
+
}
|
|
296
|
+
return edits;
|
|
297
|
+
}
|
|
298
|
+
const MARKER_ANYWHERE = /\u00abredacted(?::[a-z-]{1,16}){0,2}\u00bb/g;
|
|
299
|
+
function markerSpans(text) {
|
|
300
|
+
const spans = [];
|
|
301
|
+
if (!text.includes(REDACTION_MARKER_PREFIX))
|
|
302
|
+
return spans;
|
|
303
|
+
const re = new RegExp(MARKER_ANYWHERE.source, MARKER_ANYWHERE.flags);
|
|
304
|
+
for (let hit = re.exec(text); hit !== null; hit = re.exec(text))
|
|
305
|
+
spans.push(hit.index, hit.index + hit[0].length);
|
|
306
|
+
return spans;
|
|
307
|
+
}
|
|
308
|
+
function schemelessUserinfoEdits(text) {
|
|
309
|
+
const edits = [];
|
|
310
|
+
const markers = markerSpans(text);
|
|
311
|
+
let m = 0;
|
|
312
|
+
let floor = 0;
|
|
313
|
+
for (let at = text.indexOf('@'); at !== -1; at = text.indexOf('@', at + 1)) {
|
|
314
|
+
const hostEnd = schemelessHostEnd(text, at + 1);
|
|
315
|
+
if (hostEnd === -1)
|
|
316
|
+
continue;
|
|
317
|
+
let runStart = at;
|
|
318
|
+
while (runStart > floor && !breaksUserinfo(text.charCodeAt(runStart - 1)))
|
|
319
|
+
runStart--;
|
|
320
|
+
let found = -1;
|
|
321
|
+
let colon = -1;
|
|
322
|
+
let s = runStart;
|
|
323
|
+
while (s + 2 < at) {
|
|
324
|
+
if (colon < s) {
|
|
325
|
+
colon = s;
|
|
326
|
+
while (colon < at && text.charCodeAt(colon) !== 0x3a)
|
|
327
|
+
colon++;
|
|
328
|
+
}
|
|
329
|
+
if (colon + 1 >= at)
|
|
330
|
+
break;
|
|
331
|
+
if (colon === s) {
|
|
332
|
+
s++;
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
while (m < markers.length && (markers[m + 1] ?? 0) <= colon)
|
|
336
|
+
m += 2;
|
|
337
|
+
if (m < markers.length && (markers[m] ?? 0) <= colon) {
|
|
338
|
+
s = colon + 1;
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
const prev = s === 0 ? -1 : text.charCodeAt(s - 1);
|
|
342
|
+
const midPair = prev >= 0xd800 && prev <= 0xdbff && (text.charCodeAt(s) & 0xfc00) === 0xdc00;
|
|
343
|
+
if ((prev === -1 || !blocksUserinfoStart(prev)) && !midPair) {
|
|
344
|
+
found = s;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
s++;
|
|
348
|
+
}
|
|
349
|
+
if (found === -1)
|
|
350
|
+
continue;
|
|
351
|
+
pushEdit(edits, text, found, at, REDACTED_USERINFO);
|
|
352
|
+
floor = hostEnd;
|
|
353
|
+
}
|
|
354
|
+
return edits;
|
|
355
|
+
}
|
|
356
|
+
const MAX_URL_WASH_PASSES = 4;
|
|
357
|
+
function washUrlNets(target) {
|
|
358
|
+
for (let pass = 0; pass < MAX_URL_WASH_PASSES; pass++) {
|
|
359
|
+
target.apply(urlTokenEdits(target.text));
|
|
360
|
+
const handedOff = schemelessUserinfoEdits(target.text);
|
|
361
|
+
target.apply(handedOff);
|
|
362
|
+
if (handedOff.length === 0)
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const SECRET_SCHEME_WORD = /(?:\b|(?<=\\[A-Za-z"']))(bearer|basic)[\s:=\uFF1A\uFF1D]+/gi;
|
|
367
|
+
const SECRET_LABELLED_WORD = /(?:\b|(?<=_)|(?<=\\[A-Za-z"']))((?:access[-_ ]?|refresh[-_ ]?|id[-_ ]?|client[-_ ]?|api[-_ ]?|x[-_]api[-_ ]?|session[-_ ]?|auth[-_ ]?)?(?:token|key|secret|password|authorization|credential)s?)(?:\\*["'])?\s*[:=\uFF1A\uFF1D]\s*/gi;
|
|
368
|
+
const DIAGNOSTIC_VALUE_WORDS = new Set([
|
|
369
|
+
'missing', 'required', 'invalid', 'expired', 'unauthorized', 'unauthenticated', 'authentication',
|
|
370
|
+
'authorization', 'credential', 'credentials', 'unsupported', 'malformed', 'rejected', 'absent',
|
|
371
|
+
'empty', 'undefined', 'null', 'none', 'unknown', 'forbidden', 'redacted', 'notprovided', 'notfound',
|
|
372
|
+
'mismatch', 'revoked', 'disabled', 'unavailable', 'incorrect',
|
|
373
|
+
'not', 'no', 'set', 'unset', 'blank', 'value', 'values', 'of', 'the', 'a', 'an', 'is', 'was', 'are',
|
|
374
|
+
'for', 'to', 'in', 'on', 'and', 'or', 'from', 'with', 'at', 'by', 'this', 'that', 'it', 'be', 'must',
|
|
375
|
+
'should', 'can', 'cannot', 'true', 'false', 'yes', 'ok', 'n/a', 'na',
|
|
376
|
+
'bearer', 'basic', 'digest', 'hmac', 'oauth', 'oauth2', 'jwt', 'apikey', 'api-key',
|
|
377
|
+
]);
|
|
378
|
+
const SCHEME_FOLLOWER_WORDS = new Set([
|
|
379
|
+
'token', 'tokens', 'auth', 'authentication', 'authorization', 'header', 'headers', 'scheme', 'schemes', 'realm',
|
|
380
|
+
'challenge', 'access', 'login', 'prefix', 'format', 'type', 'mode', 'method', 'flow', 'grant', 'usage', 'example',
|
|
381
|
+
'setup', 'plan', 'tier', 'info', 'information', 'support', 'level', 'string', 'config', 'configuration', 'settings',
|
|
382
|
+
'version', 'check', 'validation', 'error', 'errors', 'failed', 'failure', 'credentials', 'user', 'username',
|
|
383
|
+
]);
|
|
384
|
+
const AUTH_SCHEME_WORDS = new Set([
|
|
385
|
+
'bearer', 'basic', 'digest', 'token', 'bot', 'ssws', 'api-key', 'apikey', 'key', 'hmac', 'oauth', 'oauth2', 'jwt',
|
|
386
|
+
'negotiate', 'ntlm', 'hawk', 'mac', 'dsn', 'splunk', 'sapisidhash', 'googlelogin', 'aws', 'client-id', 'pat',
|
|
387
|
+
]);
|
|
388
|
+
const AUTH_SIGNATURE_SCHEME = /^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+){2,}$/;
|
|
389
|
+
const BARE_VALUE_STOP_CHARS = new Set([',', ';', '"', "'", '\\', '(', ')', '[', ']', '{', '}', '<', '>']);
|
|
390
|
+
const isBareValueStop = (ch) => ch <= ' ' || BARE_VALUE_STOP_CHARS.has(ch);
|
|
391
|
+
class InnerWordIndex {
|
|
392
|
+
text;
|
|
393
|
+
probes;
|
|
394
|
+
constructor(text) {
|
|
395
|
+
this.text = text;
|
|
396
|
+
this.probes = [
|
|
397
|
+
{ re: new RegExp(SECRET_LABELLED_WORD.source, SECRET_LABELLED_WORD.flags), scheme: false, from: -1, hit: null },
|
|
398
|
+
{ re: new RegExp(SECRET_SCHEME_WORD.source, SECRET_SCHEME_WORD.flags), scheme: true, from: -1, hit: null },
|
|
399
|
+
];
|
|
400
|
+
}
|
|
401
|
+
next(q) {
|
|
402
|
+
let best = null;
|
|
403
|
+
for (const p of this.probes) {
|
|
404
|
+
const reusable = p.from !== -1 && q >= p.from && (p.hit === null || q <= p.hit.start);
|
|
405
|
+
if (!reusable) {
|
|
406
|
+
p.re.lastIndex = q;
|
|
407
|
+
const m = p.re.exec(this.text);
|
|
408
|
+
p.from = q;
|
|
409
|
+
if (m === null)
|
|
410
|
+
p.hit = null;
|
|
411
|
+
else {
|
|
412
|
+
const sep = p.scheme ? (m[1] ?? '').length : m[0].search(/[:=:=]/);
|
|
413
|
+
p.hit = { start: m.index, sepAt: m.index + Math.max(0, sep) };
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (p.hit !== null && (best === null || p.hit.start < best.start))
|
|
417
|
+
best = p.hit;
|
|
418
|
+
}
|
|
419
|
+
return best;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
function innerWordFits(text, w) {
|
|
423
|
+
for (let k = w.start; k <= w.sepAt && k < text.length; k++)
|
|
424
|
+
if (isBareValueStop(text.charAt(k)))
|
|
425
|
+
return false;
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
const isCutDelimiter = (ch) => ch === '&' || ch === '|' || ch === '.';
|
|
429
|
+
function scanSecretValue(text, from, idx) {
|
|
430
|
+
let i = from;
|
|
431
|
+
let slashes = 0;
|
|
432
|
+
while (i < text.length && text[i] === '\\') {
|
|
433
|
+
i++;
|
|
434
|
+
slashes++;
|
|
435
|
+
}
|
|
436
|
+
const q = text[i];
|
|
437
|
+
if (q === '"' || q === "'") {
|
|
438
|
+
const open = i + 1;
|
|
439
|
+
const trimmed = (end) => {
|
|
440
|
+
let a = open;
|
|
441
|
+
let b = end;
|
|
442
|
+
while (a < b && text[a] === VIEW_SEPARATOR)
|
|
443
|
+
a++;
|
|
444
|
+
while (b > a && text[b - 1] === VIEW_SEPARATOR)
|
|
445
|
+
b--;
|
|
446
|
+
return b > a ? { start: a, end: b } : null;
|
|
447
|
+
};
|
|
448
|
+
if (slashes > 0) {
|
|
449
|
+
const close = text.indexOf('\\' + q, open);
|
|
450
|
+
return trimmed(close === -1 ? text.length : close);
|
|
451
|
+
}
|
|
452
|
+
let j = open;
|
|
453
|
+
while (j < text.length) {
|
|
454
|
+
if (text[j] === '\\') {
|
|
455
|
+
j += 2;
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
if (text[j] === q)
|
|
459
|
+
return trimmed(j);
|
|
460
|
+
j++;
|
|
461
|
+
}
|
|
462
|
+
return trimmed(text.length);
|
|
463
|
+
}
|
|
464
|
+
const markerAt = new RegExp(MARKER_ANYWHERE.source, 'y');
|
|
465
|
+
markerAt.lastIndex = i;
|
|
466
|
+
const leadMarker = markerAt.exec(text);
|
|
467
|
+
if (leadMarker !== null) {
|
|
468
|
+
const k = i + leadMarker[0].length;
|
|
469
|
+
if (/^(?:&+[^\s&=#"'<>`|\\^{}]{1,128}=|#\u00abredacted:)/.test(text.slice(k, k + 160)))
|
|
470
|
+
return { start: i, end: k };
|
|
471
|
+
}
|
|
472
|
+
const head = text.slice(i, i + 64);
|
|
473
|
+
const cuttable = idx !== undefined && !ADDRESS_SCHEME_RE.test(head) && !withoutSeparators(head).startsWith(`${REDACTED_USERINFO}@`);
|
|
474
|
+
let pending = cuttable ? idx.next(i + 1) : null;
|
|
475
|
+
let cut = false;
|
|
476
|
+
let j = i;
|
|
477
|
+
while (j < text.length) {
|
|
478
|
+
if (pending !== null && j >= pending.start) {
|
|
479
|
+
if (j === pending.start && innerWordFits(text, pending)) {
|
|
480
|
+
cut = true;
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
pending = null;
|
|
484
|
+
}
|
|
485
|
+
const ch = text.charAt(j);
|
|
486
|
+
if (isBareValueStop(ch))
|
|
487
|
+
break;
|
|
488
|
+
if (ch === VIEW_SEPARATOR) {
|
|
489
|
+
let p = j;
|
|
490
|
+
while (p < text.length && text[p] === VIEW_SEPARATOR)
|
|
491
|
+
p++;
|
|
492
|
+
if (credentialWordStartsNear(text, p))
|
|
493
|
+
break;
|
|
494
|
+
j = p;
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
j++;
|
|
498
|
+
}
|
|
499
|
+
while (j > i && text[j - 1] === VIEW_SEPARATOR)
|
|
500
|
+
j--;
|
|
501
|
+
if (cut) {
|
|
502
|
+
while (j > i && isCutDelimiter(text.charAt(j - 1))) {
|
|
503
|
+
j--;
|
|
504
|
+
while (j > i && text[j - 1] === VIEW_SEPARATOR)
|
|
505
|
+
j--;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return j > i ? { start: i, end: j } : null;
|
|
509
|
+
}
|
|
510
|
+
const ADDRESS_SCHEME_RE = /^[a-z][a-z0-9+.-]{1,15}:\/{2}/i;
|
|
511
|
+
function isDiagnosticValue(value, raw = value) {
|
|
512
|
+
if (MARKER_RE.test(value) || value === RETIRED_MARKER)
|
|
513
|
+
return true;
|
|
514
|
+
if (MARKER_RE.test(value.replace(/^\.+/, '')))
|
|
515
|
+
return true;
|
|
516
|
+
if (value.startsWith(`${REDACTED_USERINFO}@`))
|
|
517
|
+
return true;
|
|
518
|
+
if (ADDRESS_SCHEME_RE.test(raw))
|
|
519
|
+
return true;
|
|
520
|
+
const bare = value.replace(/^\.+/, '').replace(/[.,;:!?)\]}'"]{0,8}$/, '').toLowerCase();
|
|
521
|
+
return bare === '' || DIAGNOSTIC_VALUE_WORDS.has(bare);
|
|
522
|
+
}
|
|
523
|
+
const TOKEN_COUNT_HEAD_WORDS = new Set([
|
|
524
|
+
'max', 'min', 'num', 'n', 'input', 'output', 'total', 'prompt', 'completion', 'thinking', 'reasoning',
|
|
525
|
+
'cache', 'cached', 'context', 'budget', 'used', 'remaining', 'reserved', 'new', 'effective', 'limit',
|
|
526
|
+
'sampled', 'generated', 'response', 'request', 'cumulative', 'image', 'audio', 'text', 'tool',
|
|
527
|
+
]);
|
|
528
|
+
const isIdentOrDash = (ch) => (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch === '_' || ch === '-';
|
|
529
|
+
function isTokenCountLabel(text, at, label) {
|
|
530
|
+
if (label.toLowerCase() !== 'tokens' || at === 0)
|
|
531
|
+
return false;
|
|
532
|
+
const sep = text.charAt(at - 1);
|
|
533
|
+
if (sep !== '_' && sep !== '-')
|
|
534
|
+
return false;
|
|
535
|
+
let i = at - 1;
|
|
536
|
+
while (i > 0 && isIdentOrDash(text.charAt(i - 1)))
|
|
537
|
+
i--;
|
|
538
|
+
const head = text.slice(i, at - 1).toLowerCase();
|
|
539
|
+
const lastSep = Math.max(head.lastIndexOf('_'), head.lastIndexOf('-'));
|
|
540
|
+
return TOKEN_COUNT_HEAD_WORDS.has(head.slice(lastSep + 1));
|
|
541
|
+
}
|
|
542
|
+
const BACKTICKED_CREDENTIAL_NAME = /^\x60[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+\x60[.,;:!?)]*$/;
|
|
543
|
+
const CREDENTIAL_NAME_SEGMENTS = new Set([
|
|
544
|
+
'KEY', 'KEYS', 'TOKEN', 'TOKENS', 'SECRET', 'SECRETS', 'PASSWORD', 'PASSWD', 'PWD', 'CREDENTIAL', 'CREDENTIALS',
|
|
545
|
+
'AUTH', 'APIKEY', 'PAT',
|
|
546
|
+
]);
|
|
547
|
+
function isBacktickedCredentialName(value) {
|
|
548
|
+
if (!BACKTICKED_CREDENTIAL_NAME.test(value))
|
|
549
|
+
return false;
|
|
550
|
+
const name = value.slice(1, value.indexOf('`', 1));
|
|
551
|
+
return name.split('_').some(seg => CREDENTIAL_NAME_SEGMENTS.has(seg));
|
|
552
|
+
}
|
|
553
|
+
const KEY_NAME_SHAPE = /^(?:[a-z]+(?:[A-Z][a-z]+)*|[a-z]+(?:_[a-z]+)+)$/;
|
|
554
|
+
function isNamedNotValue(label, value) {
|
|
555
|
+
const l = label.toLowerCase();
|
|
556
|
+
const core = value.replace(/[.,;:!?)]+$/, '');
|
|
557
|
+
if (l === 'tokens' && /^[0-9]+$/.test(core))
|
|
558
|
+
return true;
|
|
559
|
+
if (l === 'keys' && core.length <= 40 && KEY_NAME_SHAPE.test(core))
|
|
560
|
+
return true;
|
|
561
|
+
return isBacktickedCredentialName(value);
|
|
562
|
+
}
|
|
563
|
+
function labelStartsAt(text, at) {
|
|
564
|
+
const re = new RegExp(SECRET_LABELLED_WORD.source, 'iy');
|
|
565
|
+
re.lastIndex = at;
|
|
566
|
+
return re.test(text);
|
|
567
|
+
}
|
|
568
|
+
function credentialWordStartsAt(text, at) {
|
|
569
|
+
if (at >= text.length)
|
|
570
|
+
return false;
|
|
571
|
+
if (labelStartsAt(text, at))
|
|
572
|
+
return true;
|
|
573
|
+
const re = new RegExp(SECRET_SCHEME_WORD.source, 'iy');
|
|
574
|
+
re.lastIndex = at;
|
|
575
|
+
return re.test(text);
|
|
576
|
+
}
|
|
577
|
+
function credentialWordStartsNear(text, at) {
|
|
578
|
+
return credentialWordStartsAt(text, at) || credentialWordStartsAt(text, at + 1);
|
|
579
|
+
}
|
|
580
|
+
function percentDecodeForMatching(text) {
|
|
581
|
+
const chars = [];
|
|
582
|
+
const offsets = [];
|
|
583
|
+
let i = 0;
|
|
584
|
+
while (i < text.length) {
|
|
585
|
+
if (text.charAt(i) === '%' && isHexDigit(text.charAt(i + 1)) && isHexDigit(text.charAt(i + 2))) {
|
|
586
|
+
const code = Number.parseInt(text.slice(i + 1, i + 3), 16);
|
|
587
|
+
if (code < 0x80) {
|
|
588
|
+
chars.push(String.fromCharCode(code));
|
|
589
|
+
offsets.push(i);
|
|
590
|
+
i += 3;
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
chars.push(text.charAt(i));
|
|
595
|
+
offsets.push(i);
|
|
596
|
+
i += 1;
|
|
597
|
+
}
|
|
598
|
+
offsets.push(text.length);
|
|
599
|
+
return { decoded: chars.join(''), offsets };
|
|
600
|
+
}
|
|
601
|
+
function firstLabelCutInPath(path) {
|
|
602
|
+
let cut = -1;
|
|
603
|
+
for (const src of [SECRET_SCHEME_WORD, SECRET_LABELLED_WORD]) {
|
|
604
|
+
const hit = new RegExp(src.source, src.flags.replace('g', '')).exec(path);
|
|
605
|
+
if (hit === null)
|
|
606
|
+
continue;
|
|
607
|
+
const at = hit.index + hit[0].length;
|
|
608
|
+
if (at >= path.length)
|
|
609
|
+
continue;
|
|
610
|
+
if (cut === -1 || at < cut)
|
|
611
|
+
cut = at;
|
|
612
|
+
}
|
|
613
|
+
return cut;
|
|
614
|
+
}
|
|
615
|
+
function pathCutAfterFirstLabel(path) {
|
|
616
|
+
let cut = firstLabelCutInPath(path);
|
|
617
|
+
const { decoded, offsets } = percentDecodeForMatching(path);
|
|
618
|
+
if (decoded !== path) {
|
|
619
|
+
const decodedCut = firstLabelCutInPath(decoded);
|
|
620
|
+
if (decodedCut !== -1) {
|
|
621
|
+
const mapped = offsets[decodedCut] ?? path.length;
|
|
622
|
+
if (mapped < path.length && (cut === -1 || mapped < cut))
|
|
623
|
+
cut = mapped;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
return cut;
|
|
627
|
+
}
|
|
628
|
+
function hostTokenIsLabel(afterUserinfo, hostEnd) {
|
|
629
|
+
const re = new RegExp(SECRET_LABELLED_WORD.source, SECRET_LABELLED_WORD.flags);
|
|
630
|
+
for (let hit = re.exec(afterUserinfo); hit !== null && hit.index <= hostEnd; hit = re.exec(afterUserinfo)) {
|
|
631
|
+
const sepEnd = hit.index + hit[0].length;
|
|
632
|
+
const rest = afterUserinfo.slice(sepEnd);
|
|
633
|
+
const bareColon = hit[0].endsWith(':') && !/[\s=\uFF1A\uFF1D]/.test(hit[0]);
|
|
634
|
+
const hostBefore = afterUserinfo.slice(0, sepEnd - 1);
|
|
635
|
+
const port = /^([0-9]{1,5})(?:[/?#]|$)/.exec(rest);
|
|
636
|
+
const hostNameOnly = /^[A-Za-z0-9\u00a1-\uffff](?:[A-Za-z0-9\u00a1-\uffff-]*[A-Za-z0-9\u00a1-\uffff])?(?:\.[A-Za-z0-9\u00a1-\uffff](?:[A-Za-z0-9\u00a1-\uffff-]*[A-Za-z0-9\u00a1-\uffff])?)*$/;
|
|
637
|
+
const portLike = bareColon && hostNameOnly.test(hostBefore) && port !== null && Number(port[1]) <= 65535;
|
|
638
|
+
if (!portLike)
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
function exemptAddressEdits(src, from, to, edits) {
|
|
644
|
+
const value = src.slice(from, to);
|
|
645
|
+
const q = value.search(/[?#]/);
|
|
646
|
+
const prefix = q === -1 ? value : value.slice(0, q);
|
|
647
|
+
const scheme = ADDRESS_SCHEME_RE.exec(prefix)?.[0] ?? '';
|
|
648
|
+
const slash = prefix.indexOf('/', scheme.length);
|
|
649
|
+
const rawAuthority = slash === -1 ? prefix.slice(scheme.length) : prefix.slice(scheme.length, slash);
|
|
650
|
+
const at = rawAuthority.lastIndexOf('@');
|
|
651
|
+
if (at !== -1)
|
|
652
|
+
pushEdit(edits, src, from + scheme.length, from + scheme.length + at, REDACTED_USERINFO);
|
|
653
|
+
const hostPort = at === -1 ? rawAuthority : `${REDACTED_USERINFO}@${rawAuthority.slice(at + 1)}`;
|
|
654
|
+
const userinfoLen = at === -1 ? 0 : REDACTED_USERINFO.length + 1;
|
|
655
|
+
const hostEnd = /^(?:\[[0-9A-Fa-f:.]*\]|[A-Za-z0-9._\u00a1-\uffff-]*)(?::[A-Za-z0-9]*)?/.exec(hostPort.slice(userinfoLen))?.[0].length ?? 0;
|
|
656
|
+
const cut = hostTokenIsLabel(hostPort.slice(userinfoLen), hostEnd) ? userinfoLen : userinfoLen + hostEnd;
|
|
657
|
+
const pathStart = scheme.length + (at === -1 ? cut : at + 1 + (cut - userinfoLen));
|
|
658
|
+
const pathEnd = q === -1 ? value.length : q;
|
|
659
|
+
const pathCut = pathCutAfterFirstLabel(value.slice(pathStart, pathEnd));
|
|
660
|
+
if (pathCut !== -1)
|
|
661
|
+
pushEdit(edits, src, from + pathStart + pathCut, from + pathEnd, REDACTED_SECRET);
|
|
662
|
+
if (q !== -1)
|
|
663
|
+
pushEdit(edits, src, from + q + 1, to, value.charAt(q) === '#' ? REDACTED_FRAGMENT : REDACTED_QUERY);
|
|
664
|
+
}
|
|
665
|
+
function isAddressShaped(value, raw = value) {
|
|
666
|
+
return value.startsWith(`${REDACTED_USERINFO}@`) || ADDRESS_SCHEME_RE.test(raw);
|
|
667
|
+
}
|
|
668
|
+
function addressExtentEnd(text, from) {
|
|
669
|
+
let end = from;
|
|
670
|
+
while (end < text.length) {
|
|
671
|
+
const ch = text.charAt(end);
|
|
672
|
+
if (ch === '"' || ch === "'") {
|
|
673
|
+
const prev = end > from ? text.charAt(end - 1) : '';
|
|
674
|
+
let bs = 0;
|
|
675
|
+
for (let k = end - 1; k >= from && text[k] === '\\'; k--)
|
|
676
|
+
bs++;
|
|
677
|
+
if (bs % 2 === 1) {
|
|
678
|
+
let j = end + 1;
|
|
679
|
+
let close = -1;
|
|
680
|
+
let outer = -1;
|
|
681
|
+
while (j < text.length) {
|
|
682
|
+
if (text[j] === ch) {
|
|
683
|
+
let b = 0;
|
|
684
|
+
for (let k = j - 1; k > end && text[k] === '\\'; k--)
|
|
685
|
+
b++;
|
|
686
|
+
if (b % 2 === 1) {
|
|
687
|
+
close = j;
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
outer = j;
|
|
691
|
+
break;
|
|
692
|
+
}
|
|
693
|
+
j++;
|
|
694
|
+
}
|
|
695
|
+
if (close !== -1) {
|
|
696
|
+
end = close + 1;
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
end = outer === -1 ? text.length : outer;
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
const next = text.charAt(end + 1);
|
|
703
|
+
const looksClosing = next === '' || next === ',' || next === '}' || next === ']' || next === ':' || next <= ' ';
|
|
704
|
+
if ((prev !== '=' && prev !== ':') || looksClosing)
|
|
705
|
+
break;
|
|
706
|
+
let j = end + 1;
|
|
707
|
+
while (j < text.length && text[j] !== ch)
|
|
708
|
+
j += text[j] === '\\' ? 2 : 1;
|
|
709
|
+
end = Math.min(j + 1, text.length);
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
if (ch <= ' ')
|
|
713
|
+
break;
|
|
714
|
+
end++;
|
|
715
|
+
}
|
|
716
|
+
return end;
|
|
717
|
+
}
|
|
718
|
+
function trailingLabelStart(text, from, end) {
|
|
719
|
+
const window = text.slice(from, Math.min(text.length, end + 12));
|
|
720
|
+
const segLen = end - from;
|
|
721
|
+
let best = -1;
|
|
722
|
+
for (const src of [SECRET_SCHEME_WORD, SECRET_LABELLED_WORD]) {
|
|
723
|
+
const re = new RegExp(src.source, src.flags);
|
|
724
|
+
for (let hit = re.exec(window); hit !== null && hit.index < segLen; hit = re.exec(window)) {
|
|
725
|
+
if (hit.index + hit[0].length >= segLen && (best === -1 || hit.index < best))
|
|
726
|
+
best = hit.index;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return best;
|
|
730
|
+
}
|
|
731
|
+
const withoutSeparators = (s) => (s.includes(VIEW_SEPARATOR) ? s.split(VIEW_SEPARATOR).join('') : s);
|
|
732
|
+
const isHorizontalSpace = (ch) => ch === ' ' || ch === '\t' || ch === '\u00a0' || ch === VIEW_SEPARATOR;
|
|
733
|
+
function labelEdits(text, re) {
|
|
734
|
+
const edits = [];
|
|
735
|
+
const schemePass = re === SECRET_SCHEME_WORD;
|
|
736
|
+
const idx = new InnerWordIndex(text);
|
|
737
|
+
re.lastIndex = 0;
|
|
738
|
+
for (let hit = re.exec(text); hit !== null; hit = re.exec(text)) {
|
|
739
|
+
const label = hit[1] ?? '';
|
|
740
|
+
if (isTokenCountLabel(text, hit.index, label))
|
|
741
|
+
continue;
|
|
742
|
+
const from = hit.index + hit[0].length;
|
|
743
|
+
if (credentialWordStartsAt(text, from)) {
|
|
744
|
+
re.lastIndex = from;
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
const v = scanSecretValue(text, from, idx);
|
|
748
|
+
if (v === null)
|
|
749
|
+
continue;
|
|
750
|
+
if (text.startsWith('-----BEGIN ', v.start)) {
|
|
751
|
+
re.lastIndex = v.end;
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
const rawValue = text.slice(v.start, v.end);
|
|
755
|
+
const value = withoutSeparators(rawValue);
|
|
756
|
+
const quoted = v.start > 0 && (text[v.start - 1] === '"' || text[v.start - 1] === "'");
|
|
757
|
+
if (schemePass && !quoted && SCHEME_FOLLOWER_WORDS.has(value.replace(/[.,;:!?)]+$/, '').toLowerCase())) {
|
|
758
|
+
re.lastIndex = v.end;
|
|
759
|
+
continue;
|
|
760
|
+
}
|
|
761
|
+
if (!schemePass && isNamedNotValue(label, value)) {
|
|
762
|
+
re.lastIndex = v.end;
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
if (!schemePass && !quoted && /authorizations?$/i.test(label) && isAuthScheme(value)) {
|
|
766
|
+
let j = v.end;
|
|
767
|
+
while (j < text.length && isHorizontalSpace(text.charAt(j)))
|
|
768
|
+
j++;
|
|
769
|
+
if (j > v.end && isAuthParamAt(text, j, value)) {
|
|
770
|
+
re.lastIndex = v.end;
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
const cred = j > v.end && !labelStartsAt(text, j) ? scanSecretValue(text, j, idx) : null;
|
|
774
|
+
if (cred !== null) {
|
|
775
|
+
const credValue = withoutSeparators(text.slice(cred.start, cred.end));
|
|
776
|
+
const keep = isMarker(credValue) || DIAGNOSTIC_VALUE_WORDS.has(credValue.replace(/[.,;:!?)\]}\x27\x22]+$/, '').toLowerCase());
|
|
777
|
+
if (!keep)
|
|
778
|
+
pushEdit(edits, text, cred.start, cred.end, REDACTED_SECRET);
|
|
779
|
+
re.lastIndex = cred.end;
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
re.lastIndex = v.end;
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
if (isDiagnosticValue(value, rawValue)) {
|
|
786
|
+
if (isAddressShaped(value, rawValue)) {
|
|
787
|
+
let end = quoted ? v.end : addressExtentEnd(text, v.start);
|
|
788
|
+
const trailing = trailingLabelStart(text, v.start, end);
|
|
789
|
+
if (trailing !== -1)
|
|
790
|
+
end = v.start + trailing;
|
|
791
|
+
exemptAddressEdits(text, v.start, end, edits);
|
|
792
|
+
re.lastIndex = end;
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
pushEdit(edits, text, v.start, v.end, REDACTED_SECRET);
|
|
798
|
+
}
|
|
799
|
+
re.lastIndex = v.end;
|
|
800
|
+
}
|
|
801
|
+
return edits;
|
|
802
|
+
}
|
|
803
|
+
function isAuthParamAt(text, j, scheme) {
|
|
804
|
+
const p = /^[A-Za-z][A-Za-z0-9_-]*([ \t\ufeff]*)=([ \t\ufeff]*)("|[^\s=,"]+)([ \t\ufeff]*,)?/.exec(text.slice(j, j + 512));
|
|
805
|
+
if (p === null)
|
|
806
|
+
return false;
|
|
807
|
+
if (p[1] === '' && p[2] === '')
|
|
808
|
+
return true;
|
|
809
|
+
if (!/^(?:digest|oauth|hawk|mac|hmac)$/i.test(scheme) && !AUTH_SIGNATURE_SCHEME.test(scheme))
|
|
810
|
+
return false;
|
|
811
|
+
return !(p[1] === '' && p[3] !== '"' && p[4] === undefined);
|
|
812
|
+
}
|
|
813
|
+
function isAuthScheme(value) {
|
|
814
|
+
if (isMarker(value))
|
|
815
|
+
return false;
|
|
816
|
+
return AUTH_SCHEME_WORDS.has(value.toLowerCase()) || AUTH_SIGNATURE_SCHEME.test(value);
|
|
817
|
+
}
|
|
818
|
+
const isKeyWordChar = (c) => (c >= 0x30 && c <= 0x39) || (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) || c === 0x5f || c === 0x2d;
|
|
819
|
+
const isAlnumCode = (c) => (c >= 0x30 && c <= 0x39) || (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a);
|
|
820
|
+
const PROSE_LITERAL_MIN_RUN = 16;
|
|
821
|
+
function longestAlnumRun(word) {
|
|
822
|
+
let best = 0;
|
|
823
|
+
let run = 0;
|
|
824
|
+
for (let k = 0; k < word.length; k++) {
|
|
825
|
+
if (isAlnumCode(word.charCodeAt(k))) {
|
|
826
|
+
run++;
|
|
827
|
+
if (run > best)
|
|
828
|
+
best = run;
|
|
829
|
+
}
|
|
830
|
+
else
|
|
831
|
+
run = 0;
|
|
832
|
+
}
|
|
833
|
+
return best;
|
|
834
|
+
}
|
|
835
|
+
const JWT_WORD = /^eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{4,}$/;
|
|
836
|
+
const PEM_BEGIN_SOURCE = '-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----';
|
|
837
|
+
const PEM_END_MARK = '-----END';
|
|
838
|
+
function lineBreakLengthAt(text, at) {
|
|
839
|
+
if (text.startsWith('\r\n', at))
|
|
840
|
+
return 2;
|
|
841
|
+
const c = text.charAt(at);
|
|
842
|
+
if (c === '\n' || c === '\r')
|
|
843
|
+
return 1;
|
|
844
|
+
if (text.startsWith('\\r\\n', at))
|
|
845
|
+
return 4;
|
|
846
|
+
if (text.startsWith('\\n', at) || text.startsWith('\\r', at))
|
|
847
|
+
return 2;
|
|
848
|
+
return 0;
|
|
849
|
+
}
|
|
850
|
+
function lineBreakLengthBefore(text, at) {
|
|
851
|
+
if (at >= 2 && text.startsWith('\r\n', at - 2))
|
|
852
|
+
return 2;
|
|
853
|
+
const c = text.charAt(at - 1);
|
|
854
|
+
if (c === '\n' || c === '\r')
|
|
855
|
+
return 1;
|
|
856
|
+
if (at >= 4 && text.startsWith('\\r\\n', at - 4))
|
|
857
|
+
return 4;
|
|
858
|
+
if (at >= 2 && (text.startsWith('\\n', at - 2) || text.startsWith('\\r', at - 2)))
|
|
859
|
+
return 2;
|
|
860
|
+
return 0;
|
|
861
|
+
}
|
|
862
|
+
function pemBodyEndWithoutTrailer(text, from) {
|
|
863
|
+
let pos = from;
|
|
864
|
+
let inBody = false;
|
|
865
|
+
let end = from;
|
|
866
|
+
while (pos < text.length) {
|
|
867
|
+
let le = pos;
|
|
868
|
+
while (le < text.length && lineBreakLengthAt(text, le) === 0)
|
|
869
|
+
le++;
|
|
870
|
+
const line = text.slice(pos, le);
|
|
871
|
+
if (line.trim() === '') {
|
|
872
|
+
if (inBody)
|
|
873
|
+
break;
|
|
874
|
+
}
|
|
875
|
+
else if (!inBody && /^[A-Za-z][A-Za-z0-9-]*:\s/.test(line)) {
|
|
876
|
+
end = le;
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
inBody = true;
|
|
880
|
+
const quote = line.indexOf('"');
|
|
881
|
+
if (quote !== -1) {
|
|
882
|
+
end = pos + quote;
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
end = le;
|
|
886
|
+
}
|
|
887
|
+
if (le >= text.length)
|
|
888
|
+
break;
|
|
889
|
+
pos = le + lineBreakLengthAt(text, le);
|
|
890
|
+
}
|
|
891
|
+
return end;
|
|
892
|
+
}
|
|
893
|
+
function pemEdits(text) {
|
|
894
|
+
const edits = [];
|
|
895
|
+
if (!text.includes('-----BEGIN '))
|
|
896
|
+
return edits;
|
|
897
|
+
const begin = new RegExp(PEM_BEGIN_SOURCE, 'g');
|
|
898
|
+
let nextEnd = -2;
|
|
899
|
+
for (let b = begin.exec(text); b !== null; b = begin.exec(text)) {
|
|
900
|
+
let bodyStart = b.index + b[0].length;
|
|
901
|
+
const lead = lineBreakLengthAt(text, bodyStart);
|
|
902
|
+
if (lead > 0)
|
|
903
|
+
bodyStart += lead;
|
|
904
|
+
else
|
|
905
|
+
while (bodyStart < text.length && (text[bodyStart] === ' ' || text[bodyStart] === '\t'))
|
|
906
|
+
bodyStart++;
|
|
907
|
+
if (nextEnd !== -1 && nextEnd < bodyStart)
|
|
908
|
+
nextEnd = text.indexOf(PEM_END_MARK, bodyStart);
|
|
909
|
+
let bodyEnd;
|
|
910
|
+
if (nextEnd === -1)
|
|
911
|
+
bodyEnd = pemBodyEndWithoutTrailer(text, bodyStart);
|
|
912
|
+
else {
|
|
913
|
+
bodyEnd = nextEnd;
|
|
914
|
+
const back = lineBreakLengthBefore(text, bodyEnd);
|
|
915
|
+
if (back > 0 && bodyEnd - back >= bodyStart)
|
|
916
|
+
bodyEnd -= back;
|
|
917
|
+
}
|
|
918
|
+
if (bodyEnd > bodyStart)
|
|
919
|
+
pushEdit(edits, text, bodyStart, bodyEnd, REDACTED_SECRET);
|
|
920
|
+
begin.lastIndex = Math.max(begin.lastIndex, bodyEnd);
|
|
921
|
+
}
|
|
922
|
+
return edits;
|
|
923
|
+
}
|
|
924
|
+
function authParamEdits(text) {
|
|
925
|
+
const edits = [];
|
|
926
|
+
if (!/authorization/i.test(text))
|
|
927
|
+
return edits;
|
|
928
|
+
const header = /(?:\b|(?<=_))(?:proxy-)?authorization["']?[ \t]*[:=]\s*["']?[A-Za-z][A-Za-z0-9._~+/-]*\s+/gi;
|
|
929
|
+
const param = /([A-Za-z][A-Za-z0-9_-]*)[ \t]*=[ \t]*("(?:[^"\\\n]|\\.)*"?|[^\s,"]*)\s*,?\s*/dy;
|
|
930
|
+
const signed = /^(?:response|signature|oauth_signature)$/i;
|
|
931
|
+
for (let h = header.exec(text); h !== null; h = header.exec(text)) {
|
|
932
|
+
let pos = h.index + h[0].length;
|
|
933
|
+
for (;;) {
|
|
934
|
+
param.lastIndex = pos;
|
|
935
|
+
const p = param.exec(text);
|
|
936
|
+
if (p === null || p[0].length === 0)
|
|
937
|
+
break;
|
|
938
|
+
const span = p.indices?.[2];
|
|
939
|
+
if (span !== undefined && signed.test(p[1] ?? '')) {
|
|
940
|
+
let a = span[0];
|
|
941
|
+
let b = span[1];
|
|
942
|
+
if (text[a] === '"') {
|
|
943
|
+
a++;
|
|
944
|
+
if (b > a && text[b - 1] === '"')
|
|
945
|
+
b--;
|
|
946
|
+
}
|
|
947
|
+
if (b > a && !isMarker(text.slice(a, b)))
|
|
948
|
+
pushEdit(edits, text, a, b, REDACTED_SECRET);
|
|
949
|
+
}
|
|
950
|
+
pos = p.index + p[0].length;
|
|
951
|
+
}
|
|
952
|
+
header.lastIndex = Math.max(header.lastIndex, pos);
|
|
953
|
+
}
|
|
954
|
+
return edits;
|
|
955
|
+
}
|
|
956
|
+
function proseLiteralEdits(text) {
|
|
957
|
+
const edits = [];
|
|
958
|
+
let i = 0;
|
|
959
|
+
while (i < text.length) {
|
|
960
|
+
const c = text.charCodeAt(i);
|
|
961
|
+
if (!isKeyWordChar(c) || (i > 0 && isKeyWordChar(text.charCodeAt(i - 1)))) {
|
|
962
|
+
i++;
|
|
963
|
+
continue;
|
|
964
|
+
}
|
|
965
|
+
let end = i;
|
|
966
|
+
while (end < text.length && isKeyWordChar(text.charCodeAt(end)))
|
|
967
|
+
end++;
|
|
968
|
+
const word = text.slice(i, end);
|
|
969
|
+
if (word.startsWith('eyJ')) {
|
|
970
|
+
let segEnd = end;
|
|
971
|
+
let segments = 1;
|
|
972
|
+
while (segments < 4 && text.charAt(segEnd) === '.' && segEnd + 1 < text.length && isKeyWordChar(text.charCodeAt(segEnd + 1))) {
|
|
973
|
+
segEnd++;
|
|
974
|
+
while (segEnd < text.length && isKeyWordChar(text.charCodeAt(segEnd)))
|
|
975
|
+
segEnd++;
|
|
976
|
+
segments++;
|
|
977
|
+
}
|
|
978
|
+
if (segments === 3 && JWT_WORD.test(text.slice(i, segEnd))) {
|
|
979
|
+
pushEdit(edits, text, i, segEnd, REDACTED_SECRET);
|
|
980
|
+
i = segEnd;
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
const prev = i > 0 ? text.charAt(i - 1) : '';
|
|
985
|
+
const hostSlot = prev === '/' && i > 1 && text.charAt(i - 2) === '/';
|
|
986
|
+
const pathish = (prev === '/' && !hostSlot) || prev === '\\' || prev === '.';
|
|
987
|
+
if (!pathish && SECRET_LITERAL_RE.test(word) && longestAlnumRun(word) >= PROSE_LITERAL_MIN_RUN) {
|
|
988
|
+
pushEdit(edits, text, i, end, REDACTED_SECRET);
|
|
989
|
+
}
|
|
990
|
+
i = end;
|
|
991
|
+
}
|
|
992
|
+
return edits;
|
|
993
|
+
}
|
|
994
|
+
function washWordNets(target) {
|
|
995
|
+
target.apply(pemEdits(target.text));
|
|
996
|
+
target.apply(labelEdits(target.text, SECRET_SCHEME_WORD));
|
|
997
|
+
target.apply(labelEdits(target.text, SECRET_LABELLED_WORD));
|
|
998
|
+
target.apply(authParamEdits(target.text));
|
|
999
|
+
target.apply(proseLiteralEdits(target.text));
|
|
1000
|
+
}
|
|
1001
|
+
function runCredentialNets(target, urls, words) {
|
|
1002
|
+
if (urls)
|
|
1003
|
+
washUrlNets(target);
|
|
1004
|
+
if (words)
|
|
1005
|
+
washWordNets(target);
|
|
1006
|
+
}
|
|
1007
|
+
const NEEDS_VIEW = /[\u0000-\u0008\u000E-\u001F\u007F-\u009F]|\p{Cf}|\\u/u;
|
|
1008
|
+
const FORMAT_CHAR = /^\p{Cf}$/u;
|
|
1009
|
+
const isStrippableControl = (c) => (c <= 0x1f && (c < 0x09 || c > 0x0d)) || (c >= 0x7f && c <= 0x9f);
|
|
1010
|
+
function csiEnd(text, from) {
|
|
1011
|
+
let j = from;
|
|
1012
|
+
while (j < text.length && text.charCodeAt(j) >= 0x30 && text.charCodeAt(j) <= 0x3f)
|
|
1013
|
+
j++;
|
|
1014
|
+
while (j < text.length && text.charCodeAt(j) >= 0x20 && text.charCodeAt(j) <= 0x2f)
|
|
1015
|
+
j++;
|
|
1016
|
+
const fin = text.charCodeAt(j);
|
|
1017
|
+
return fin >= 0x40 && fin <= 0x7e ? j + 1 : -1;
|
|
1018
|
+
}
|
|
1019
|
+
const MAX_CONTROL_STRING = 4096;
|
|
1020
|
+
class ControlStringEnds {
|
|
1021
|
+
text;
|
|
1022
|
+
terminator;
|
|
1023
|
+
lineBreak;
|
|
1024
|
+
termFrom = -1;
|
|
1025
|
+
termHit = null;
|
|
1026
|
+
breakFrom = -1;
|
|
1027
|
+
breakAt = -1;
|
|
1028
|
+
constructor(text) {
|
|
1029
|
+
this.text = text;
|
|
1030
|
+
this.terminator = /[\u0007\u009c]|\u001b\\|\\[uU](?:0007|009[cC]|001[bB]\\)/g;
|
|
1031
|
+
this.lineBreak = /[\n\r]/g;
|
|
1032
|
+
}
|
|
1033
|
+
find(from) {
|
|
1034
|
+
if (this.termFrom === -1 || from < this.termFrom || (this.termHit !== null && from > this.termHit.index)) {
|
|
1035
|
+
this.terminator.lastIndex = from;
|
|
1036
|
+
this.termHit = this.terminator.exec(this.text);
|
|
1037
|
+
this.termFrom = from;
|
|
1038
|
+
}
|
|
1039
|
+
if (this.breakFrom === -1 || from < this.breakFrom || (this.breakAt !== -1 && from > this.breakAt)) {
|
|
1040
|
+
this.lineBreak.lastIndex = from;
|
|
1041
|
+
const b = this.lineBreak.exec(this.text);
|
|
1042
|
+
this.breakAt = b === null ? -1 : b.index;
|
|
1043
|
+
this.breakFrom = from;
|
|
1044
|
+
}
|
|
1045
|
+
const hit = this.termHit;
|
|
1046
|
+
if (hit === null || hit.index >= Math.min(this.text.length, from + MAX_CONTROL_STRING))
|
|
1047
|
+
return null;
|
|
1048
|
+
if (this.breakAt !== -1 && this.breakAt < hit.index)
|
|
1049
|
+
return null;
|
|
1050
|
+
return { contentEnd: hit.index, end: hit.index + hit[0].length };
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
function controlString(from, ends) {
|
|
1054
|
+
const s = ends.find(from);
|
|
1055
|
+
return s === null ? { end: from } : { end: s.end, content: [from, s.contentEnd] };
|
|
1056
|
+
}
|
|
1057
|
+
function escapeSequenceAfterEsc(text, at, ends) {
|
|
1058
|
+
const n = text.charCodeAt(at);
|
|
1059
|
+
if (n === 0x5b) {
|
|
1060
|
+
const e = csiEnd(text, at + 1);
|
|
1061
|
+
return e === -1 ? null : { end: e };
|
|
1062
|
+
}
|
|
1063
|
+
if (n === 0x5d || n === 0x50 || n === 0x58 || n === 0x5e || n === 0x5f)
|
|
1064
|
+
return controlString(at + 1, ends);
|
|
1065
|
+
if (n === 0x3a || n === 0x3d || (n >= 0x60 && n <= 0x7e))
|
|
1066
|
+
return null;
|
|
1067
|
+
let j = at;
|
|
1068
|
+
while (j < text.length && text.charCodeAt(j) >= 0x20 && text.charCodeAt(j) <= 0x2f)
|
|
1069
|
+
j++;
|
|
1070
|
+
const f = text.charCodeAt(j);
|
|
1071
|
+
return f >= 0x30 && f <= 0x7e ? { end: j + 1 } : null;
|
|
1072
|
+
}
|
|
1073
|
+
function c1SequenceAfter(text, c1, at, ends) {
|
|
1074
|
+
if (c1 === 0x9b) {
|
|
1075
|
+
const e = csiEnd(text, at);
|
|
1076
|
+
return e === -1 ? null : { end: e };
|
|
1077
|
+
}
|
|
1078
|
+
if (c1 === 0x9d || c1 === 0x90 || c1 === 0x98 || c1 === 0x9e || c1 === 0x9f)
|
|
1079
|
+
return controlString(at, ends);
|
|
1080
|
+
return null;
|
|
1081
|
+
}
|
|
1082
|
+
function isFaceMarkedCodePoint(cp) {
|
|
1083
|
+
if (cp <= 0x1f || (cp >= 0x7f && cp <= 0x9f))
|
|
1084
|
+
return true;
|
|
1085
|
+
if (cp >= 0xd800 && cp <= 0xdfff)
|
|
1086
|
+
return true;
|
|
1087
|
+
if (cp === 0x2028 || cp === 0x2029)
|
|
1088
|
+
return true;
|
|
1089
|
+
return cp >= 0xad && cp <= 0x10ffff && FORMAT_CHAR.test(String.fromCodePoint(cp));
|
|
1090
|
+
}
|
|
1091
|
+
function escapeTokenAt(text, at) {
|
|
1092
|
+
const four = /^\\u([0-9A-Fa-f]{4})/.exec(text.slice(at, at + 6));
|
|
1093
|
+
const braced = four === null ? /^\\u\{([0-9A-Fa-f]{1,6})\}/.exec(text.slice(at, at + 11)) : null;
|
|
1094
|
+
const hit = four ?? braced;
|
|
1095
|
+
if (hit === null)
|
|
1096
|
+
return null;
|
|
1097
|
+
const cp = Number.parseInt(hit[1] ?? '', 16);
|
|
1098
|
+
return cp <= 0x10ffff && isFaceMarkedCodePoint(cp) ? { end: at + hit[0].length, cp } : null;
|
|
1099
|
+
}
|
|
1100
|
+
function buildCredentialView(text, separated, wordLetters = false) {
|
|
1101
|
+
if (!NEEDS_VIEW.test(text))
|
|
1102
|
+
return null;
|
|
1103
|
+
const chars = [];
|
|
1104
|
+
const starts = [];
|
|
1105
|
+
const ends = [];
|
|
1106
|
+
let changed = false;
|
|
1107
|
+
let splitAny = false;
|
|
1108
|
+
const stringEnds = new ControlStringEnds(text);
|
|
1109
|
+
const keep = (ch, s, e) => {
|
|
1110
|
+
chars.push(ch);
|
|
1111
|
+
starts.push(s);
|
|
1112
|
+
ends.push(e);
|
|
1113
|
+
};
|
|
1114
|
+
const drop = (s, e, placeholder = VIEW_SEPARATOR) => {
|
|
1115
|
+
changed = true;
|
|
1116
|
+
if (separated)
|
|
1117
|
+
keep(placeholder, s, e);
|
|
1118
|
+
};
|
|
1119
|
+
const dropUnit = (s, unit) => {
|
|
1120
|
+
const last = unit.end - 1;
|
|
1121
|
+
if (wordLetters && unit.content === undefined && last > s && credentialWordBeginsWith(text, last)) {
|
|
1122
|
+
drop(s, last);
|
|
1123
|
+
keep(text.charAt(last), last, unit.end);
|
|
1124
|
+
splitAny = true;
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
drop(s, unit.end);
|
|
1128
|
+
};
|
|
1129
|
+
let i = 0;
|
|
1130
|
+
while (i < text.length) {
|
|
1131
|
+
const c = text.charCodeAt(i);
|
|
1132
|
+
if (c === 0x1b || c === 0x9b || c === 0x9d || c === 0x90 || c === 0x98 || c === 0x9e || c === 0x9f) {
|
|
1133
|
+
const unit = c === 0x1b ? escapeSequenceAfterEsc(text, i + 1, stringEnds) : c1SequenceAfter(text, c, i + 1, stringEnds);
|
|
1134
|
+
if (unit !== null) {
|
|
1135
|
+
dropUnit(i, unit);
|
|
1136
|
+
i = unit.end;
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
drop(i, i + 1);
|
|
1140
|
+
i++;
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
if (c === 0x5c && text.charCodeAt(i + 1) === 0x75) {
|
|
1144
|
+
const tok = escapeTokenAt(text, i);
|
|
1145
|
+
if (tok !== null) {
|
|
1146
|
+
const unit = tok.cp === 0x1b
|
|
1147
|
+
? escapeSequenceAfterEsc(text, tok.end, stringEnds)
|
|
1148
|
+
: tok.cp >= 0x90 && tok.cp <= 0x9f
|
|
1149
|
+
? c1SequenceAfter(text, tok.cp, tok.end, stringEnds)
|
|
1150
|
+
: null;
|
|
1151
|
+
if (unit !== null) {
|
|
1152
|
+
dropUnit(i, unit);
|
|
1153
|
+
i = unit.end;
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
drop(i, tok.end, ' ');
|
|
1157
|
+
i = tok.end;
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
if (isStrippableControl(c)) {
|
|
1162
|
+
drop(i, i + 1);
|
|
1163
|
+
i++;
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
if (c >= 0xad) {
|
|
1167
|
+
const cp = text.codePointAt(i) ?? c;
|
|
1168
|
+
const width = cp > 0xffff ? 2 : 1;
|
|
1169
|
+
if (FORMAT_CHAR.test(String.fromCodePoint(cp))) {
|
|
1170
|
+
drop(i, i + width);
|
|
1171
|
+
i += width;
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
for (let u = 0; u < width; u++)
|
|
1175
|
+
keep(text.charAt(i + u), i + u, i + u + 1);
|
|
1176
|
+
i += width;
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
keep(text.charAt(i), i, i + 1);
|
|
1180
|
+
i++;
|
|
1181
|
+
}
|
|
1182
|
+
if (!changed || (wordLetters && !splitAny))
|
|
1183
|
+
return null;
|
|
1184
|
+
return { view: chars.join(''), starts: Int32Array.from(starts), ends: Int32Array.from(ends) };
|
|
1185
|
+
}
|
|
1186
|
+
const isProbeFiller = (c) => c === 0x5c || c === 0x22 || c === 0x27 || isRegexSpace(c);
|
|
1187
|
+
function credentialWordBeginsWith(text, at) {
|
|
1188
|
+
const c = text.charCodeAt(at);
|
|
1189
|
+
const letter = (c >= 0x41 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a);
|
|
1190
|
+
if (!letter && c !== 0x5c)
|
|
1191
|
+
return false;
|
|
1192
|
+
const n = text.charCodeAt(at + 1);
|
|
1193
|
+
const nextOk = (n >= 0x41 && n <= 0x5a) || (n >= 0x61 && n <= 0x7a) || (!letter && (n === 0x22 || n === 0x27));
|
|
1194
|
+
if (!nextOk)
|
|
1195
|
+
return false;
|
|
1196
|
+
let end = Math.min(text.length, at + 64);
|
|
1197
|
+
while (end < text.length && isProbeFiller(text.charCodeAt(end - 1)))
|
|
1198
|
+
end++;
|
|
1199
|
+
const probe = ' ' + text.slice(at, Math.min(text.length, end + 2));
|
|
1200
|
+
if (letter)
|
|
1201
|
+
return credentialWordStartsAt(probe, 1);
|
|
1202
|
+
return credentialWordStartsAt(probe, 2) || credentialWordStartsAt(probe, 3);
|
|
1203
|
+
}
|
|
1204
|
+
function rebuildFromView(original, view, target) {
|
|
1205
|
+
const out = [];
|
|
1206
|
+
const viewLength = view.view.length;
|
|
1207
|
+
let cursor = 0;
|
|
1208
|
+
let k = 0;
|
|
1209
|
+
const text = target.text;
|
|
1210
|
+
while (k < text.length) {
|
|
1211
|
+
const s = target.src[k] ?? 0;
|
|
1212
|
+
if (s >= 0) {
|
|
1213
|
+
const end = view.ends[s] ?? cursor;
|
|
1214
|
+
if ((view.starts[s] ?? 0) >= cursor) {
|
|
1215
|
+
out.push(original.slice(cursor, end));
|
|
1216
|
+
cursor = end;
|
|
1217
|
+
}
|
|
1218
|
+
else
|
|
1219
|
+
out.push(text.charAt(k));
|
|
1220
|
+
k++;
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
let j = k + 1;
|
|
1224
|
+
while (j < text.length && (target.src[j] ?? 0) < 0)
|
|
1225
|
+
j++;
|
|
1226
|
+
let a = target.rangeStart(k);
|
|
1227
|
+
let b = target.rangeEnd(k);
|
|
1228
|
+
for (let t = k + 1; t < j; t++) {
|
|
1229
|
+
a = Math.min(a, target.rangeStart(t));
|
|
1230
|
+
b = Math.max(b, target.rangeEnd(t));
|
|
1231
|
+
}
|
|
1232
|
+
const from = a < viewLength ? (view.starts[a] ?? original.length) : original.length;
|
|
1233
|
+
const to = b > a ? (view.ends[b - 1] ?? from) : from;
|
|
1234
|
+
if (from > cursor)
|
|
1235
|
+
out.push(original.slice(cursor, from));
|
|
1236
|
+
out.push(text.slice(k, j));
|
|
1237
|
+
cursor = Math.max(cursor, to);
|
|
1238
|
+
k = j;
|
|
1239
|
+
}
|
|
1240
|
+
out.push(original.slice(cursor));
|
|
1241
|
+
return out.join('');
|
|
1242
|
+
}
|
|
1243
|
+
function washThroughView(original, view, urls, words) {
|
|
1244
|
+
const target = new TrackedTarget(view.view, view.view.length);
|
|
1245
|
+
runCredentialNets(target, urls, words);
|
|
1246
|
+
return target.text === view.view ? original : rebuildFromView(original, view, target);
|
|
1247
|
+
}
|
|
1248
|
+
function washCredentialNetsOnce(text, urls, words, nested) {
|
|
1249
|
+
const contentsWashed = nested ? text : washControlStringContents(text, urls, words);
|
|
1250
|
+
const lettered = buildCredentialView(contentsWashed, true, true);
|
|
1251
|
+
const pre = lettered === null ? contentsWashed : washThroughView(contentsWashed, lettered, urls, words);
|
|
1252
|
+
const separated = buildCredentialView(pre, true);
|
|
1253
|
+
if (separated === null) {
|
|
1254
|
+
const target = new PlainTarget(pre);
|
|
1255
|
+
runCredentialNets(target, urls, words);
|
|
1256
|
+
return target.text;
|
|
1257
|
+
}
|
|
1258
|
+
const afterSeparated = washThroughView(pre, separated, urls, words);
|
|
1259
|
+
const joined = buildCredentialView(afterSeparated, false);
|
|
1260
|
+
return joined === null ? afterSeparated : washThroughView(afterSeparated, joined, urls, words);
|
|
1261
|
+
}
|
|
1262
|
+
function washControlStringContents(text, urls, words) {
|
|
1263
|
+
if (!/[\u001b\u0090\u0098\u009d-\u009f]|\\u/.test(text))
|
|
1264
|
+
return text;
|
|
1265
|
+
const ends = new ControlStringEnds(text);
|
|
1266
|
+
const parts = [];
|
|
1267
|
+
let cursor = 0;
|
|
1268
|
+
let i = 0;
|
|
1269
|
+
while (i < text.length) {
|
|
1270
|
+
const c = text.charCodeAt(i);
|
|
1271
|
+
let unit = null;
|
|
1272
|
+
let next = i + 1;
|
|
1273
|
+
if (c === 0x1b)
|
|
1274
|
+
unit = escapeSequenceAfterEsc(text, i + 1, ends);
|
|
1275
|
+
else if (c >= 0x90 && c <= 0x9f)
|
|
1276
|
+
unit = c1SequenceAfter(text, c, i + 1, ends);
|
|
1277
|
+
else if (c === 0x5c && text.charCodeAt(i + 1) === 0x75) {
|
|
1278
|
+
const tok = escapeTokenAt(text, i);
|
|
1279
|
+
if (tok !== null) {
|
|
1280
|
+
next = tok.end;
|
|
1281
|
+
unit = tok.cp === 0x1b ? escapeSequenceAfterEsc(text, tok.end, ends) : tok.cp >= 0x90 && tok.cp <= 0x9f ? c1SequenceAfter(text, tok.cp, tok.end, ends) : null;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (unit === null) {
|
|
1285
|
+
i = next;
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
if (unit.content !== undefined) {
|
|
1289
|
+
const [a, b] = unit.content;
|
|
1290
|
+
const inner = text.slice(a, b);
|
|
1291
|
+
const washed = washCredentialNets(inner, urls, words, true);
|
|
1292
|
+
if (washed !== inner) {
|
|
1293
|
+
parts.push(text.slice(cursor, a), washed);
|
|
1294
|
+
cursor = b;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
i = Math.max(unit.end, i + 1);
|
|
1298
|
+
}
|
|
1299
|
+
if (cursor === 0)
|
|
1300
|
+
return text;
|
|
1301
|
+
parts.push(text.slice(cursor));
|
|
1302
|
+
return parts.join('');
|
|
1303
|
+
}
|
|
1304
|
+
const MAX_SETTLE_ROUNDS = 4;
|
|
1305
|
+
function washCredentialNets(text, urls, words, nested = false) {
|
|
1306
|
+
if (!urls && !words)
|
|
1307
|
+
return text;
|
|
1308
|
+
let current = text;
|
|
1309
|
+
for (let round = 0; round < MAX_SETTLE_ROUNDS; round++) {
|
|
1310
|
+
const next = washCredentialNetsOnce(current, urls, words, nested);
|
|
1311
|
+
if (next === current)
|
|
1312
|
+
return current;
|
|
1313
|
+
current = next;
|
|
1314
|
+
}
|
|
1315
|
+
return current;
|
|
1316
|
+
}
|
|
1317
|
+
export function washCredentials(text) {
|
|
1318
|
+
if (typeof text !== 'string')
|
|
1319
|
+
return text;
|
|
1320
|
+
return washCredentialNets(text, true, true);
|
|
1321
|
+
}
|
|
1322
|
+
const C0_ALL = '\\u0000-\\u001F';
|
|
1323
|
+
const C0_KEEP_LAYOUT = '\\u0000-\\u0008\\u000B-\\u001F';
|
|
1324
|
+
const DEL_C1 = '\\u007F-\\u009F';
|
|
1325
|
+
const LONE_SURROGATE = '\\uD800-\\uDFFF';
|
|
1326
|
+
const LINE_SEPARATORS = '\\u2028\\u2029';
|
|
1327
|
+
const FORMAT_CLASS = '(?![\\u200C\\u200D])\\p{Cf}';
|
|
1328
|
+
const RGI_TAG_FLAG = '\\u{1F3F4}\\u{E0067}\\u{E0062}(?:\\u{E0065}\\u{E006E}\\u{E0067}|\\u{E0073}\\u{E0063}\\u{E0074}|\\u{E0077}\\u{E006C}\\u{E0073})\\u{E007F}';
|
|
1329
|
+
const BIDI_REORDER = '\\u061C\\u200E\\u200F\\u2028\\u2029\\u202A-\\u202E\\u2066-\\u2069';
|
|
1330
|
+
function unsafeClass(members, withFormatClass) {
|
|
1331
|
+
if (members === '' && !withFormatClass)
|
|
1332
|
+
return null;
|
|
1333
|
+
const parts = [
|
|
1334
|
+
...(withFormatClass ? [RGI_TAG_FLAG] : []),
|
|
1335
|
+
...(members !== '' ? [`[${members}]`] : []),
|
|
1336
|
+
...(withFormatClass ? [FORMAT_CLASS] : []),
|
|
1337
|
+
];
|
|
1338
|
+
return new RegExp(parts.join('|'), 'gu');
|
|
1339
|
+
}
|
|
1340
|
+
export const LEGACY_ESCAPE_FACE = Object.freeze({
|
|
1341
|
+
unsafe: unsafeClass(`${C0_ALL}${DEL_C1}${BIDI_REORDER}${LONE_SURROGATE}`, false),
|
|
1342
|
+
fold: false,
|
|
1343
|
+
mark: 'escape',
|
|
1344
|
+
});
|
|
1345
|
+
export const LEGACY_LABEL_FACE = Object.freeze({ ...LEGACY_ESCAPE_FACE, fold: true });
|
|
1346
|
+
export const LEGACY_NAME_FACE = Object.freeze({
|
|
1347
|
+
unsafe: unsafeClass(`${C0_ALL}\\u007F${LINE_SEPARATORS}`, false),
|
|
1348
|
+
fold: true,
|
|
1349
|
+
mark: 'space',
|
|
1350
|
+
});
|
|
1351
|
+
function markOf(ch, mark) {
|
|
1352
|
+
if (mark === 'dot')
|
|
1353
|
+
return '.';
|
|
1354
|
+
if (mark === 'space')
|
|
1355
|
+
return ' ';
|
|
1356
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
1357
|
+
const hex = code.toString(16).toUpperCase();
|
|
1358
|
+
return code > 0xffff ? `\\u{${hex}}` : `\\u${hex.padStart(4, '0')}`;
|
|
1359
|
+
}
|
|
1360
|
+
const foldWhitespace = (s) => s.replace(/\s+/g, ' ').trim();
|
|
1361
|
+
export function applyDisplayFace(text, face) {
|
|
1362
|
+
const markAll = (s) => face.unsafe === null ? s : s.replace(face.unsafe, ch => (ch.length > 2 ? ch : markOf(ch, face.mark)));
|
|
1363
|
+
if (face.mark === 'space') {
|
|
1364
|
+
const marked = markAll(text);
|
|
1365
|
+
return face.fold ? foldWhitespace(marked) : marked;
|
|
1366
|
+
}
|
|
1367
|
+
return markAll(face.fold ? foldWhitespace(text) : text);
|
|
1368
|
+
}
|
|
1369
|
+
const PUBLIC_UNSAFE_BY_NETS = Object.freeze(Array.from({ length: 8 }, (_, i) => {
|
|
1370
|
+
const controls = (i & 4) !== 0;
|
|
1371
|
+
const bidi = (i & 2) !== 0;
|
|
1372
|
+
const keep = (i & 1) !== 0;
|
|
1373
|
+
const members = `${controls ? `${keep ? C0_KEEP_LAYOUT : C0_ALL}${DEL_C1}${LONE_SURROGATE}` : ''}${bidi ? LINE_SEPARATORS : ''}`;
|
|
1374
|
+
return unsafeClass(members, bidi);
|
|
1375
|
+
}));
|
|
1376
|
+
function publicFace(opts) {
|
|
1377
|
+
const fold = opts.foldLines !== false;
|
|
1378
|
+
const keep = !fold && opts.keepLayout === true;
|
|
1379
|
+
const index = (opts.controls !== false ? 4 : 0) + (opts.bidi !== false ? 2 : 0) + (keep ? 1 : 0);
|
|
1380
|
+
return { unsafe: PUBLIC_UNSAFE_BY_NETS[index] ?? null, fold, mark: opts.mark ?? 'escape' };
|
|
1381
|
+
}
|
|
1382
|
+
const ESCAPE_TOKEN_AT = /^\\u(?:[0-9A-F]{4}|\{[0-9A-F]{1,6}\})/;
|
|
1383
|
+
function capDisplay(text, max, mark) {
|
|
1384
|
+
if (typeof max !== 'number' || !Number.isFinite(max))
|
|
1385
|
+
return text;
|
|
1386
|
+
const limit = Math.max(0, Math.floor(max));
|
|
1387
|
+
if (text.length <= limit)
|
|
1388
|
+
return text;
|
|
1389
|
+
let cut = limit;
|
|
1390
|
+
if (mark === 'escape') {
|
|
1391
|
+
for (let p = cut - 1; p >= 0 && p >= cut - 10; p--) {
|
|
1392
|
+
if (text.charAt(p) !== '\\')
|
|
1393
|
+
continue;
|
|
1394
|
+
const token = ESCAPE_TOKEN_AT.exec(text.slice(p, p + 11));
|
|
1395
|
+
if (token !== null && p + token[0].length > cut)
|
|
1396
|
+
cut = p;
|
|
1397
|
+
break;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
if (cut > 0) {
|
|
1401
|
+
const code = text.charCodeAt(cut - 1);
|
|
1402
|
+
if (code >= 0xd800 && code <= 0xdbff)
|
|
1403
|
+
cut -= 1;
|
|
1404
|
+
}
|
|
1405
|
+
return text.slice(0, cut);
|
|
1406
|
+
}
|
|
1407
|
+
const MAX_CAP_SETTLE_ROUNDS = 4;
|
|
1408
|
+
export function displayUntrusted(text, opts = {}) {
|
|
1409
|
+
const input = typeof text === 'string' ? text : String(text ?? '');
|
|
1410
|
+
const o = opts ?? {};
|
|
1411
|
+
const urls = o.credentialUrls !== false;
|
|
1412
|
+
const words = o.credentialWords !== false;
|
|
1413
|
+
const face = publicFace(o);
|
|
1414
|
+
const settle = (s) => {
|
|
1415
|
+
let current = applyDisplayFace(washCredentialNets(s, urls, words), face);
|
|
1416
|
+
if (current === s)
|
|
1417
|
+
return current;
|
|
1418
|
+
for (let round = 1; round < MAX_SETTLE_ROUNDS; round++) {
|
|
1419
|
+
const next = applyDisplayFace(washCredentialNets(current, urls, words), face);
|
|
1420
|
+
if (next === current)
|
|
1421
|
+
return current;
|
|
1422
|
+
current = next;
|
|
1423
|
+
}
|
|
1424
|
+
return current;
|
|
1425
|
+
};
|
|
1426
|
+
const settled = settle(input);
|
|
1427
|
+
let out = capDisplay(settled, o.max, face.mark);
|
|
1428
|
+
if (out === settled)
|
|
1429
|
+
return out;
|
|
1430
|
+
for (let round = 0; round < MAX_CAP_SETTLE_ROUNDS; round++) {
|
|
1431
|
+
const again = capDisplay(settle(out), o.max, face.mark);
|
|
1432
|
+
if (again === out)
|
|
1433
|
+
break;
|
|
1434
|
+
out = again;
|
|
1435
|
+
}
|
|
1436
|
+
return out;
|
|
1437
|
+
}
|