@sema-agent/server 7.56.0 → 7.57.0

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 (110) hide show
  1. package/README.md +3 -2
  2. package/README.zh-CN.md +2 -2
  3. package/USAGE.md +101 -9
  4. package/dist/approval-ask-audit-store.d.ts +100 -1
  5. package/dist/approval-ask-audit-store.js +103 -2
  6. package/dist/approval-card.d.ts +148 -13
  7. package/dist/approval-card.js +82 -13
  8. package/dist/approval.d.ts +31 -0
  9. package/dist/approval.js +18 -0
  10. package/dist/auto-mode-face.d.ts +111 -0
  11. package/dist/auto-mode-face.js +99 -0
  12. package/dist/bench/s1/arms.js +5 -5
  13. package/dist/bench/s1/live-deps.js +11 -11
  14. package/dist/bench/s1/run-firm.js +3 -0
  15. package/dist/bench/s1/runner-ctx.d.ts +4 -0
  16. package/dist/bench/s1/runner-ctx.js +7 -0
  17. package/dist/boot/config-center.js +23 -1
  18. package/dist/boot/coordinators.js +13 -1
  19. package/dist/boot/parked-revive-gate.d.ts +7 -2
  20. package/dist/boot/parked-revive-gate.js +12 -1
  21. package/dist/boot/resolve-spec.d.ts +3 -0
  22. package/dist/boot/resolve-spec.js +3 -1
  23. package/dist/boot/runner-deps.d.ts +12 -0
  24. package/dist/boot/runner-deps.js +28 -3
  25. package/dist/boot/runtime-caps.d.ts +19 -9
  26. package/dist/boot/runtime-caps.js +49 -21
  27. package/dist/boot/session-shell-gate-registry.d.ts +39 -0
  28. package/dist/boot/session-shell-gate-registry.js +21 -0
  29. package/dist/config-catalog.js +14 -5
  30. package/dist/config-types.d.ts +41 -10
  31. package/dist/config.d.ts +21 -0
  32. package/dist/config.js +40 -5
  33. package/dist/http/route-ctx.d.ts +75 -0
  34. package/dist/http/routes/a2a-serve.js +5 -3
  35. package/dist/http/routes/approvals-assistant.js +19 -6
  36. package/dist/http/routes/capabilities.js +21 -4
  37. package/dist/http/routes/memory-origin.d.ts +2 -2
  38. package/dist/http/routes/rules.js +3 -2
  39. package/dist/http/routes/runs.d.ts +0 -14
  40. package/dist/http/routes/runs.js +20 -8
  41. package/dist/http/routes/tasks.js +34 -8
  42. package/dist/http/routes/workflows.js +19 -4
  43. package/dist/http/server.d.ts +11 -1
  44. package/dist/http/server.js +215 -43
  45. package/dist/http/wire-types.d.ts +9 -4
  46. package/dist/leader/diffout.js +2 -1
  47. package/dist/leader/endpoint.js +45 -12
  48. package/dist/leader/fanout.js +6 -5
  49. package/dist/leader/leader.js +17 -14
  50. package/dist/leader/merge.js +17 -12
  51. package/dist/leader/planner.js +3 -2
  52. package/dist/leader/repair-oracle.js +6 -4
  53. package/dist/leader/repair-wire.js +5 -3
  54. package/dist/leader/wire.d.ts +30 -1
  55. package/dist/leader/wire.js +36 -19
  56. package/dist/main.js +43 -7
  57. package/dist/observability/err-text.d.ts +6 -0
  58. package/dist/observability/err-text.js +10 -0
  59. package/dist/observability/fail-open.d.ts +40 -0
  60. package/dist/observability/fail-open.js +19 -0
  61. package/dist/observability/metrics.js +1 -1
  62. package/dist/observability/run-terminal-log.d.ts +120 -0
  63. package/dist/observability/run-terminal-log.js +360 -0
  64. package/dist/orchestration/workflow-completion-inbox.d.ts +9 -0
  65. package/dist/orchestration/workflow-completion-inbox.js +8 -0
  66. package/dist/permission-rule-vocab.d.ts +40 -0
  67. package/dist/permission-rule-vocab.js +17 -0
  68. package/dist/plugins/checkpoint-store-sql.d.ts +14 -0
  69. package/dist/plugins/checkpoint-store-sql.js +19 -3
  70. package/dist/plugins/file-run-store.d.ts +3 -34
  71. package/dist/plugins/file-run-store.js +19 -0
  72. package/dist/plugins/local-checkpoint-store.d.ts +10 -0
  73. package/dist/plugins/local-checkpoint-store.js +3 -0
  74. package/dist/plugins/memory-run-store.d.ts +3 -15
  75. package/dist/plugins/memory-run-store.js +19 -0
  76. package/dist/plugins/permission-rule-store-file.js +8 -3
  77. package/dist/plugins/permission-rule-store-sql.js +16 -10
  78. package/dist/plugins/remote-scratchpad.js +3 -2
  79. package/dist/plugins/run-store-sql.d.ts +3 -42
  80. package/dist/plugins/run-store-sql.js +30 -1
  81. package/dist/plugins/store-backend.d.ts +1 -1
  82. package/dist/plugins/store-contracts.d.ts +66 -1
  83. package/dist/plugins/workflow-run-store-sql.d.ts +5 -0
  84. package/dist/plugins/workflow-run-store-sql.js +10 -2
  85. package/dist/rules-consent.js +20 -13
  86. package/dist/run-cancel-context.d.ts +13 -0
  87. package/dist/run-cancel-context.js +15 -0
  88. package/dist/run-local.js +2 -2
  89. package/dist/runs.d.ts +4 -1
  90. package/dist/runs.js +41 -10
  91. package/dist/runtime-caps-resolver.d.ts +133 -17
  92. package/dist/runtime-caps-resolver.js +41 -3
  93. package/dist/task-settings.d.ts +57 -3
  94. package/dist/task-settings.js +78 -5
  95. package/dist/task-workflow.d.ts +32 -2
  96. package/dist/task-workflow.js +8 -3
  97. package/dist/tool-approval.d.ts +26 -3
  98. package/dist/tool-approval.js +96 -13
  99. package/dist/trace/core-keyset-guard.d.ts +2 -2
  100. package/dist/trace/ledger-sink.d.ts +13 -4
  101. package/dist/trace/ledger-sink.js +14 -6
  102. package/dist/trace/project.d.ts +13 -0
  103. package/dist/trace/project.js +10 -1
  104. package/dist/trace/redact.d.ts +22 -11
  105. package/dist/trace/redact.js +655 -20
  106. package/dist/trace/sema-provenance.d.ts +26 -0
  107. package/dist/trace/sema-provenance.js +11 -0
  108. package/dist/turn-activity.d.ts +32 -2
  109. package/dist/turn-activity.js +29 -4
  110. package/package.json +2 -2
@@ -1,40 +1,675 @@
1
- const SECRET_PATTERNS = [
1
+ import { recordFailOpen } from "../observability/fail-open.js";
2
+ const REDACTION_TOKEN_RE_SRC = String.raw `«redacted(?::[a-z-]{1,16}){0,2}»`;
3
+ const FULL_SECRET_PATTERNS = [
2
4
  [/-----BEGIN[^-]+PRIVATE KEY(?: BLOCK)?-----[\s\S]{0,8192}?-----END[^-]+PRIVATE KEY(?: BLOCK)?-----/g, "«redacted:private-key»"],
5
+ [/---- BEGIN SSH2 ENCRYPTED PRIVATE KEY ----[\s\S]{0,8192}?---- END SSH2 ENCRYPTED PRIVATE KEY ----/g, "«redacted:private-key»"],
3
6
  [/\bsk-[A-Za-z0-9_-]{16,}\b/g, "«redacted:key»"],
4
7
  [/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}\b/g, "«redacted:gh-token»"],
8
+ [/\bgithub_pat_[A-Za-z0-9]{22}_[A-Za-z0-9]{59}\b/g, "«redacted:gh-token»"],
5
9
  [/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g, "«redacted:slack-token»"],
6
10
  [/\bAKIA[0-9A-Z]{16}\b/g, "«redacted:aws-key»"],
11
+ [/\bASIA[0-9A-Z]{16}\b/g, "«redacted:aws-key»"],
12
+ [/\b(?:gl(?:pat|dt|rt|rtr|cbt|ptt|oas|ft|imt|agent|wt|soat|ffct)-[A-Za-z0-9_-]{20,}|(?:sk|rk)_(?:live|test|org)_[A-Za-z0-9]{24,}|pypi-[A-Za-z0-9_-]{85,})\b/g, "«redacted:key»"],
13
+ [/\bxapp-\d-[A-Za-z0-9-]{20,}\b/g, "«redacted:slack-token»"],
7
14
  [/\b[Bb]earer\s+[A-Za-z0-9._~+/-]{20,}=*/g, "Bearer «redacted»"],
8
15
  [/\bBasic\s+[A-Za-z0-9+/]{16,}={0,2}/g, "Basic «redacted»"],
9
- [/\b([A-Za-z][A-Za-z0-9+.-]*:\/\/[^/@\s:]+):[^/@\s]+@/g, "$1:«redacted»@"],
16
+ [/:(?<=\b[A-Za-z][A-Za-z0-9+.-]*:)\/\//g, "$1:«redacted»@"],
10
17
  [/\b[Tt]oken\s+[A-Za-z0-9._~+/-]{20,}/g, "token «redacted»"],
11
18
  [/("(?:private_key|client_secret|password|api[_-]?key|secret|token)"\s*:\s*)"[^"\\]*(?:\\.[^"\\]*)*"/gi, '$1"«redacted»"'],
12
19
  ];
20
+ const PEM_FRAGMENT_TOKEN = "«redacted:private-key:partial»";
21
+ const PEM_HEADER_IDENT_MAX = 31;
22
+ const PEM_BODY_RUN_MIN = 8;
23
+ const CH_TAB = 9, CH_LF = 10, CH_CR = 13, CH_SPACE = 32, CH_PLUS = 43, CH_MINUS = 45, CH_SLASH = 47;
24
+ const CH_0 = 48, CH_9 = 57, CH_COLON = 58, CH_EQ = 61, CH_A = 65, CH_Z = 90, CH_BACKSLASH = 92, CH_UNDERSCORE = 95;
25
+ const CH_a = 97, CH_n = 110, CH_r = 114, CH_t = 116, CH_z = 122;
26
+ const CH_QUOTE = 34;
27
+ const CH_LAQUO = 171;
28
+ const isSepCode = (c) => c === CH_SPACE || c === CH_TAB || c === CH_CR || c === CH_LF;
29
+ const isBase64Code = (c) => (c >= CH_A && c <= CH_Z) || (c >= CH_a && c <= CH_z) || (c >= CH_0 && c <= CH_9) || c === CH_PLUS || c === CH_SLASH || c === CH_EQ;
30
+ const isIdentStartCode = (c) => (c >= CH_A && c <= CH_Z) || (c >= CH_a && c <= CH_z);
31
+ const isIdentCode = (c) => isIdentStartCode(c) || (c >= CH_0 && c <= CH_9) || c === CH_UNDERSCORE || c === CH_MINUS;
32
+ function pemEscapeLetterAt(s, i, limit) {
33
+ let j = i;
34
+ while (j < limit && s.charCodeAt(j) === CH_BACKSLASH)
35
+ j += 1;
36
+ if (j === i || j >= limit)
37
+ return -1;
38
+ const n = s.charCodeAt(j);
39
+ return n === CH_r || n === CH_n || n === CH_t ? j : -1;
40
+ }
41
+ function skipPemSeps(s, i, limit) {
42
+ for (;;) {
43
+ if (i >= limit)
44
+ return i;
45
+ const c = s.charCodeAt(i);
46
+ if (isSepCode(c)) {
47
+ i += 1;
48
+ continue;
49
+ }
50
+ if (c === CH_BACKSLASH) {
51
+ const j = pemEscapeLetterAt(s, i, limit);
52
+ if (j >= 0) {
53
+ i = j + 1;
54
+ continue;
55
+ }
56
+ }
57
+ return i;
58
+ }
59
+ }
60
+ function pemLineBreakLen(s, i, limit) {
61
+ if (i >= limit)
62
+ return 0;
63
+ const c = s.charCodeAt(i);
64
+ if (c === CH_CR || c === CH_LF)
65
+ return 1;
66
+ if (c === CH_BACKSLASH) {
67
+ const j = pemEscapeLetterAt(s, i, limit);
68
+ if (j >= 0 && s.charCodeAt(j) !== CH_t)
69
+ return j + 1 - i;
70
+ }
71
+ return 0;
72
+ }
73
+ function pemInlineSepLen(s, i, limit) {
74
+ if (i >= limit)
75
+ return 0;
76
+ const c = s.charCodeAt(i);
77
+ if (c === CH_SPACE || c === CH_TAB)
78
+ return 1;
79
+ if (c === CH_BACKSLASH) {
80
+ const j = pemEscapeLetterAt(s, i, limit);
81
+ if (j >= 0 && s.charCodeAt(j) === CH_t)
82
+ return j + 1 - i;
83
+ }
84
+ return 0;
85
+ }
86
+ function probePemHeaderToken(s, p, limit) {
87
+ if (p >= limit || !isIdentStartCode(s.charCodeAt(p)))
88
+ return -1;
89
+ p += 1;
90
+ let identLen = 1;
91
+ while (identLen < PEM_HEADER_IDENT_MAX && p < limit && isIdentCode(s.charCodeAt(p))) {
92
+ p += 1;
93
+ identLen += 1;
94
+ }
95
+ for (;;) {
96
+ if (p >= limit)
97
+ return -1;
98
+ const c = s.charCodeAt(p);
99
+ if (c === CH_SPACE || c === CH_TAB) {
100
+ p += 1;
101
+ continue;
102
+ }
103
+ return c === CH_COLON ? p + 1 : -1;
104
+ }
105
+ }
106
+ const PEM_HDR = { search: 0, contentEnd: 0, next: 0, hitLimit: false, jsonEnd: false };
107
+ function setPemHdr(search, contentEnd, next, hitLimit, jsonEnd = false) {
108
+ PEM_HDR.search = search;
109
+ PEM_HDR.contentEnd = contentEnd;
110
+ PEM_HDR.next = next;
111
+ PEM_HDR.hitLimit = hitLimit;
112
+ PEM_HDR.jsonEnd = jsonEnd;
113
+ return true;
114
+ }
115
+ function parsePemHeaderLine(s, i, limit, allowBare, json) {
116
+ const start = skipPemSeps(s, i, limit);
117
+ if (start >= limit)
118
+ return false;
119
+ const afterToken = probePemHeaderToken(s, start, limit);
120
+ if (afterToken < 0 && !allowBare)
121
+ return false;
122
+ let p = afterToken < 0 ? start : afterToken;
123
+ if (afterToken >= 0 && probePemHeaderToken(s, p, limit) >= 0)
124
+ return setPemHdr(start, p, p, false);
125
+ for (;;) {
126
+ if (p >= limit)
127
+ return setPemHdr(start, p, p, true);
128
+ const c = s.charCodeAt(p);
129
+ if (c !== CH_CR && c !== CH_LF && c !== CH_SPACE && c !== CH_TAB && c !== CH_BACKSLASH && c !== CH_QUOTE) {
130
+ p += 1;
131
+ continue;
132
+ }
133
+ if (c === CH_QUOTE) {
134
+ if (json)
135
+ return setPemHdr(start, p, p, false, true);
136
+ p += 1;
137
+ continue;
138
+ }
139
+ if (c === CH_CR || c === CH_LF)
140
+ return setPemHdr(start, p, p + 1, false);
141
+ if (c === CH_BACKSLASH) {
142
+ let j = p;
143
+ while (j < limit && s.charCodeAt(j) === CH_BACKSLASH)
144
+ j += 1;
145
+ const n = j < limit ? s.charCodeAt(j) : -1;
146
+ if (n !== CH_r && n !== CH_n && n !== CH_t) {
147
+ p = json && (j - p) % 2 === 0 && n === CH_QUOTE ? j : Math.min(limit, j + 1);
148
+ continue;
149
+ }
150
+ }
151
+ let q = p;
152
+ for (;;) {
153
+ const b = pemLineBreakLen(s, q, limit);
154
+ if (b === 1)
155
+ return setPemHdr(start, p, q + 1, false);
156
+ if (b >= 2) {
157
+ q += b;
158
+ continue;
159
+ }
160
+ const u = pemInlineSepLen(s, q, limit);
161
+ if (u === 0)
162
+ break;
163
+ q += u;
164
+ }
165
+ if (q >= limit)
166
+ return setPemHdr(start, p, q, true);
167
+ if (probePemHeaderToken(s, q, limit) >= 0)
168
+ return setPemHdr(start, p, q, false);
169
+ p = q;
170
+ }
171
+ }
172
+ function parsePemBody(s, i, limit) {
173
+ let end = -1;
174
+ let p = i;
175
+ for (;;) {
176
+ const q = skipPemSeps(s, p, limit);
177
+ let r = q;
178
+ while (r < limit && isBase64Code(s.charCodeAt(r)))
179
+ r += 1;
180
+ if (r - q < PEM_BODY_RUN_MIN)
181
+ break;
182
+ end = r;
183
+ p = r;
184
+ }
185
+ return end;
186
+ }
187
+ function rightmostPemRunEnd(s, from, to) {
188
+ let best = -1;
189
+ let runStart = -1;
190
+ for (let p = from; p <= to; p++) {
191
+ if (p < to && s.charCodeAt(p) === CH_LAQUO) {
192
+ const tokenEnd = redactionTokenEndAt(s, p, to);
193
+ if (tokenEnd > 0) {
194
+ if (runStart >= 0 && p - runStart >= PEM_BODY_RUN_MIN)
195
+ best = p;
196
+ runStart = -1;
197
+ p = tokenEnd - 1;
198
+ continue;
199
+ }
200
+ }
201
+ if (p < to && isBase64Code(s.charCodeAt(p))) {
202
+ if (runStart < 0)
203
+ runStart = p;
204
+ }
205
+ else if (runStart >= 0) {
206
+ if (p - runStart >= PEM_BODY_RUN_MIN)
207
+ best = p;
208
+ runStart = -1;
209
+ }
210
+ }
211
+ return best;
212
+ }
213
+ const PEM_KEY_WORD = "PRIVATE KEY";
214
+ const PEM_BLOCK_WORD = " BLOCK";
215
+ function isPemFenceLabel(s, from, to) {
216
+ let end = to;
217
+ if (end - from > PEM_BLOCK_WORD.length && s.startsWith(PEM_BLOCK_WORD, end - PEM_BLOCK_WORD.length))
218
+ end -= PEM_BLOCK_WORD.length;
219
+ if (end - from <= PEM_KEY_WORD.length)
220
+ return false;
221
+ if (!s.startsWith(PEM_KEY_WORD, end - PEM_KEY_WORD.length))
222
+ return false;
223
+ for (let i = from; i < to; i++)
224
+ if (s.charCodeAt(i) === 45)
225
+ return false;
226
+ return true;
227
+ }
228
+ const PEM_FENCE_OPEN = "-----BEGIN";
229
+ const PEM_FENCE_CLOSE = "-----";
230
+ function findPemFence(text, from) {
231
+ for (;;) {
232
+ const fence = text.indexOf(PEM_FENCE_OPEN, from);
233
+ if (fence < 0)
234
+ return undefined;
235
+ const labelFrom = fence + PEM_FENCE_OPEN.length;
236
+ const close = text.indexOf(PEM_FENCE_CLOSE, labelFrom);
237
+ if (close < 0)
238
+ return undefined;
239
+ from = labelFrom;
240
+ if (!isPemFenceLabel(text, labelFrom, close))
241
+ continue;
242
+ return { fence, after: close + PEM_FENCE_CLOSE.length };
243
+ }
244
+ }
245
+ function redactPemFragments(text, json) {
246
+ let count = 0;
247
+ let out;
248
+ let copied = 0;
249
+ let cur = findPemFence(text, 0);
250
+ let inherited = false;
251
+ while (cur !== undefined) {
252
+ const next = findPemFence(text, cur.after);
253
+ const limit = next === undefined ? text.length : next.fence;
254
+ let pos = cur.after;
255
+ let allowBare = inherited;
256
+ let hitLimit = false;
257
+ let rightmost = -1;
258
+ while (parsePemHeaderLine(text, pos, limit, allowBare, json)) {
259
+ const e = rightmostPemRunEnd(text, PEM_HDR.search, PEM_HDR.contentEnd);
260
+ if (e > rightmost)
261
+ rightmost = e;
262
+ pos = PEM_HDR.next;
263
+ allowBare = false;
264
+ if (PEM_HDR.hitLimit) {
265
+ hitLimit = true;
266
+ break;
267
+ }
268
+ if (PEM_HDR.jsonEnd)
269
+ break;
270
+ }
271
+ inherited = hitLimit;
272
+ let bodyEnd = parsePemBody(text, pos, limit);
273
+ if (bodyEnd < 0)
274
+ bodyEnd = rightmost;
275
+ if (bodyEnd >= 0) {
276
+ out ??= [];
277
+ out.push(text.slice(copied, cur.fence), PEM_FRAGMENT_TOKEN);
278
+ copied = bodyEnd;
279
+ count += 1;
280
+ }
281
+ cur = next;
282
+ }
283
+ if (out === undefined)
284
+ return { out: text, count: 0 };
285
+ out.push(text.slice(copied));
286
+ return { out: out.join(""), count };
287
+ }
288
+ const HEADLESS_PPK_TOKEN = "«redacted:private-key:partial»";
289
+ function redactHeadlessPpkBodies(text) {
290
+ const KEY = "Private-Lines: ";
291
+ const n = text.length;
292
+ const isB64 = (c) => (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 43 || c === 47 || c === 61;
293
+ const skipHs = (i) => { while (i < n && (text.charCodeAt(i) === 32 || text.charCodeAt(i) === 9))
294
+ i += 1; return i; };
295
+ const skipNewlines = (i) => {
296
+ let q = skipHs(i);
297
+ let seen = false;
298
+ for (;;) {
299
+ let any = false;
300
+ if (text.charCodeAt(q) === 13) {
301
+ q += 1;
302
+ any = true;
303
+ }
304
+ else {
305
+ const e = escapedNewlineLen(q);
306
+ if (e > 0 && text.charCodeAt(q + e - 1) === 114) {
307
+ q += e;
308
+ any = true;
309
+ }
310
+ }
311
+ if (text.charCodeAt(q) === 10) {
312
+ q += 1;
313
+ any = true;
314
+ }
315
+ else {
316
+ const e = escapedNewlineLen(q);
317
+ if (e > 0 && text.charCodeAt(q + e - 1) === 110) {
318
+ q += e;
319
+ any = true;
320
+ }
321
+ }
322
+ if (!any)
323
+ break;
324
+ seen = true;
325
+ q = skipHs(q);
326
+ }
327
+ return seen ? q : -1;
328
+ };
329
+ const escapedNewlineLen = (i) => {
330
+ let k = i;
331
+ while (k < n && text.charCodeAt(k) === 92)
332
+ k += 1;
333
+ if (k === i)
334
+ return 0;
335
+ const c = text.charCodeAt(k);
336
+ return c === 110 || c === 114 ? k - i + 1 : 0;
337
+ };
338
+ const atLineEnd = (i) => {
339
+ const b = skipHs(i);
340
+ if (b >= n)
341
+ return true;
342
+ const c = text.charCodeAt(b);
343
+ return c === 10 || c === 13 || c === 34 || escapedNewlineLen(b) > 0;
344
+ };
345
+ let out = "";
346
+ let last = 0;
347
+ let count = 0;
348
+ let search = 0;
349
+ for (;;) {
350
+ const at = text.indexOf(KEY, search);
351
+ if (at === -1)
352
+ break;
353
+ let p = at + KEY.length;
354
+ const d0 = p;
355
+ while (p < n && text.charCodeAt(p) >= 48 && text.charCodeAt(p) <= 57)
356
+ p += 1;
357
+ if (p === d0) {
358
+ search = at + KEY.length;
359
+ continue;
360
+ }
361
+ let end = p;
362
+ let lines = 0;
363
+ for (;;) {
364
+ const q0 = skipNewlines(end);
365
+ if (q0 === -1)
366
+ break;
367
+ let q = q0;
368
+ const mk = text.charCodeAt(q);
369
+ if (mk === 43 || mk === 45)
370
+ q = skipHs(q + 1);
371
+ let r = q;
372
+ while (r < n && isB64(text.charCodeAt(r)))
373
+ r += 1;
374
+ if (r - q < 4) {
375
+ r = q0;
376
+ while (r < n && isB64(text.charCodeAt(r)))
377
+ r += 1;
378
+ if (r - q0 < 4)
379
+ break;
380
+ }
381
+ if (!atLineEnd(r))
382
+ break;
383
+ lines += 1;
384
+ end = r;
385
+ }
386
+ if (lines === 0) {
387
+ search = p;
388
+ continue;
389
+ }
390
+ out += text.slice(last, at) + HEADLESS_PPK_TOKEN;
391
+ last = end;
392
+ search = end;
393
+ count += 1;
394
+ }
395
+ return count === 0 ? { out: text, count: 0 } : { out: out + text.slice(last), count };
396
+ }
397
+ const FRAGMENT_SECRET_PATTERNS = [
398
+ [/\bsk-(?=[A-Za-z0-9_-]{6,})(?=[A-Za-z0-9_-]{0,64}[0-9A-Z][A-Za-z0-9_-]{0,64}[0-9A-Z])[A-Za-z0-9_-]{6,}/g, "«redacted:key:partial»"],
399
+ [/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{8,}/g, "«redacted:gh-token:partial»"],
400
+ [/\bgithub_pat_[A-Za-z0-9_]{8,}/g, "«redacted:gh-token:partial»"],
401
+ [/\bxox[baprs]-[A-Za-z0-9-]{4,}/g, "«redacted:slack-token:partial»"],
402
+ [/\bAKIA[0-9A-Z]{6,}/g, "«redacted:aws-key:partial»"],
403
+ [/\bASIA[0-9A-Z]{8,}/g, "«redacted:aws-key:partial»"],
404
+ [/\b(?:gl(?:pat|dt|rt|rtr|cbt|ptt|oas|ft|imt|agent|wt|soat|ffct)-|(?:sk|rk)_(?:live|test|org)_|pypi-)(?=[A-Za-z0-9_-]{8,})(?=[A-Za-z0-9_-]{0,90}[0-9A-Z][A-Za-z0-9_-]{0,90}[0-9A-Z])[A-Za-z0-9_-]{8,}/g, "«redacted:key:partial»"],
405
+ [/\bxapp-\d-[A-Za-z0-9-]{4,}/g, "«redacted:slack-token:partial»"],
406
+ ];
407
+ const REDACTION_TOKENS = new Set([...FULL_SECRET_PATTERNS.map(([, r]) => r), PEM_FRAGMENT_TOKEN, ...FRAGMENT_SECRET_PATTERNS.map(([, r]) => r)]
408
+ .flatMap((r) => r.match(/«[^»]*»/g) ?? []));
409
+ const REDACTION_TOKEN_MAX = Math.max(...[...REDACTION_TOKENS].map((t) => t.length));
410
+ {
411
+ const closed = new RegExp(`^${REDACTION_TOKEN_RE_SRC}$`);
412
+ for (const t of REDACTION_TOKENS)
413
+ if (!closed.test(t))
414
+ throw new Error(`redact: token ${t} is outside the closed form ${REDACTION_TOKEN_RE_SRC}`);
415
+ }
416
+ function redactionTokenEndAt(s, p, limit) {
417
+ const max = Math.min(limit, p + REDACTION_TOKEN_MAX);
418
+ for (let e = p + 2; e <= max; e++) {
419
+ if (s.charCodeAt(e - 1) === 187)
420
+ return REDACTION_TOKENS.has(s.slice(p, e)) ? e : 0;
421
+ }
422
+ return 0;
423
+ }
424
+ const labelOfToken = (token) => token.replace(/[«»]/g, "").replace(/^redacted:/, "");
425
+ const regexRule = ([re, repl], arm) => {
426
+ const groupCount = (new RegExp(`${re.source}|`).exec("")?.length ?? 1) - 1;
427
+ const refs = [...repl.matchAll(/\$(.)/g)].map(([, d]) => d ?? "");
428
+ for (const d of refs) {
429
+ if (!/^[1-9]$/.test(d) || Number(d) > groupCount)
430
+ throw new Error(`redact: replacement ${JSON.stringify(repl)} uses unsupported ref $${d}`);
431
+ }
432
+ const expand = refs.length === 0 ? undefined : (args) => repl.replace(/\$([1-9])/g, (_, d) => args[Number(d)] ?? "");
433
+ return {
434
+ label: labelOfToken(repl),
435
+ arm,
436
+ run: (text, wantCount, json) => {
437
+ const rx = re;
438
+ if (!wantCount)
439
+ return { out: text.replace(rx, repl), count: 0 };
440
+ let n = 0;
441
+ const out = text.replace(rx, (...args) => {
442
+ n += 1;
443
+ return expand === undefined ? repl : expand(args);
444
+ });
445
+ return { out, count: n };
446
+ },
447
+ };
448
+ };
449
+ const URL_USERINFO_PATTERN = (() => {
450
+ const row = FULL_SECRET_PATTERNS.find(([, repl]) => repl.endsWith("»@"));
451
+ if (row === undefined)
452
+ throw new Error("redact: URL-userinfo rule missing from FULL_SECRET_PATTERNS");
453
+ return row;
454
+ })();
455
+ const isRegexSpace = (c) => c === 32 || (c >= 9 && c <= 13) || c === 0xa0 || c === 0x1680 || (c >= 0x2000 && c <= 0x200a) || c === 0x2028 || c === 0x2029 || c === 0x202f || c === 0x205f || c === 0x3000 || c === 0xfeff;
456
+ const isLineTerminator = (c) => c === 10 || c === 13 || c === 0x2028 || c === 0x2029;
457
+ function scanUserinfoRun(text, i, kind, json) {
458
+ const n = text.length;
459
+ while (i < n) {
460
+ const c = text.charCodeAt(i);
461
+ if (json && c === 92) {
462
+ if (i + 1 < n && !isLineTerminator(text.charCodeAt(i + 1))) {
463
+ i += 2;
464
+ continue;
465
+ }
466
+ return i;
467
+ }
468
+ if (c === 64 || isRegexSpace(c) || (json && c === 34))
469
+ return i;
470
+ switch (kind) {
471
+ case "plain-user":
472
+ if (c === 47 || c === 58)
473
+ return i;
474
+ break;
475
+ case "plain-pass":
476
+ if (c === 47)
477
+ return i;
478
+ break;
479
+ case "token-pass":
480
+ if (c === 58 && text.charCodeAt(i + 1) === 47 && text.charCodeAt(i + 2) === 47)
481
+ return i;
482
+ break;
483
+ }
484
+ i += 1;
485
+ }
486
+ return i;
487
+ }
488
+ const REDACTION_TOKEN_Y = new RegExp(REDACTION_TOKEN_RE_SRC, "y");
489
+ function matchUserinfoAt(text, p, json) {
490
+ REDACTION_TOKEN_Y.lastIndex = p;
491
+ const tokEnd = REDACTION_TOKEN_Y.test(text) ? REDACTION_TOKEN_Y.lastIndex : -1;
492
+ const afterTok = tokEnd >= 0 ? text.charCodeAt(tokEnd) : -1;
493
+ if (afterTok === 64)
494
+ return null;
495
+ let userEnd;
496
+ let kind;
497
+ if (afterTok === 58) {
498
+ userEnd = tokEnd;
499
+ kind = "token-pass";
500
+ }
501
+ else {
502
+ userEnd = scanUserinfoRun(text, p, "plain-user", json);
503
+ if (userEnd === p || text.charCodeAt(userEnd) !== 58)
504
+ return null;
505
+ kind = "plain-pass";
506
+ }
507
+ const passEnd = scanUserinfoRun(text, userEnd + 1, kind, json);
508
+ if (passEnd === userEnd + 1 || text.charCodeAt(passEnd) !== 64)
509
+ return null;
510
+ return [userEnd, passEnd + 1];
511
+ }
512
+ function redactUrlUserinfo(text, json) {
513
+ const [head, repl] = URL_USERINFO_PATTERN;
514
+ head.lastIndex = 0;
515
+ let out = "";
516
+ let last = 0;
517
+ let count = 0;
518
+ for (let m = head.exec(text); m !== null; m = head.exec(text)) {
519
+ const colon = m.index;
520
+ const hit = matchUserinfoAt(text, colon + 3, json);
521
+ if (hit === null) {
522
+ head.lastIndex = colon + 1;
523
+ continue;
524
+ }
525
+ const user = text.slice(colon, hit[0]);
526
+ out += text.slice(last, colon) + repl.replace(/\$1/g, () => user);
527
+ last = hit[1];
528
+ head.lastIndex = hit[1];
529
+ count += 1;
530
+ }
531
+ return count === 0 ? { out: text, count: 0 } : { out: out + text.slice(last), count };
532
+ }
533
+ const PPK_CONTAINER_TOKEN = "«redacted:private-key»";
534
+ let ppkScanBytes = 0;
535
+ export function ppkScanBytesForTest() { return ppkScanBytes; }
536
+ export function resetPpkScanBytesForTest() { ppkScanBytes = 0; }
537
+ function redactPpkContainers(text, record) {
538
+ const HEAD = "PuTTY-User-Key-File-";
539
+ const MAC = "Private-MAC: ";
540
+ const HASH = "Private-Hash: ";
541
+ const LINES = "Private-Lines: ";
542
+ const cache = new Map();
543
+ const find = (needle, from) => {
544
+ const c = cache.get(needle);
545
+ if (c !== undefined && c >= from)
546
+ return c;
547
+ const p = text.indexOf(needle, from);
548
+ ppkScanBytes += (p === -1 ? text.length : p) - from;
549
+ const v = p === -1 ? Number.POSITIVE_INFINITY : p;
550
+ cache.set(needle, v);
551
+ return v;
552
+ };
553
+ const isVersionedHead = (at) => {
554
+ let p = at + HEAD.length;
555
+ const start = p;
556
+ while (p < text.length) {
557
+ const c = text.charCodeAt(p);
558
+ if (c >= 48 && c <= 57)
559
+ p += 1;
560
+ else
561
+ break;
562
+ }
563
+ return p > start && text.startsWith(": ", p);
564
+ };
565
+ const nextVersionedHead = (from) => {
566
+ let p = find(HEAD, from);
567
+ while (p !== Number.POSITIVE_INFINITY && !isVersionedHead(p))
568
+ p = find(HEAD, p + HEAD.length);
569
+ return p;
570
+ };
571
+ const hexRunEnd = (from) => {
572
+ let e = from;
573
+ while (e < text.length) {
574
+ const c = text.charCodeAt(e);
575
+ if ((c >= 48 && c <= 57) || (c >= 97 && c <= 102) || (c >= 65 && c <= 70))
576
+ e += 1;
577
+ else
578
+ break;
579
+ }
580
+ return e;
581
+ };
582
+ let out = "";
583
+ let last = 0;
584
+ let count = 0;
585
+ let at = nextVersionedHead(0);
586
+ while (at !== Number.POSITIVE_INFINITY) {
587
+ let pos = at + HEAD.length + 1;
588
+ let end = text.length;
589
+ let ateToEnd = true;
590
+ for (;;) {
591
+ const mac = find(MAC, pos);
592
+ const hash = find(HASH, pos);
593
+ const term = Math.min(mac, hash);
594
+ if (term === Number.POSITIVE_INFINITY)
595
+ break;
596
+ const hexStart = term + (term === mac ? MAC.length : HASH.length);
597
+ const hexEnd = hexRunEnd(hexStart);
598
+ if (hexEnd - hexStart < 32) {
599
+ pos = hexStart;
600
+ continue;
601
+ }
602
+ if (find(LINES, at) > term) {
603
+ pos = hexStart;
604
+ continue;
605
+ }
606
+ const limit = nextVersionedHead(hexEnd);
607
+ const straggler = Math.min(find(LINES, hexEnd), find(MAC, hexEnd), find(HASH, hexEnd));
608
+ let gapBlank = true;
609
+ if (limit !== Number.POSITIVE_INFINITY) {
610
+ for (let i = hexEnd; i < limit; i++) {
611
+ const c = text.charCodeAt(i);
612
+ if (c !== 32 && c !== 10 && c !== 13 && c !== 9) {
613
+ gapBlank = false;
614
+ break;
615
+ }
616
+ }
617
+ }
618
+ ateToEnd = straggler < limit || !gapBlank;
619
+ end = ateToEnd ? text.length : hexEnd;
620
+ break;
621
+ }
622
+ if (ateToEnd && record)
623
+ recordFailOpen("server.redact.ppk-eat-to-end", `from=${at} len=${text.length}`);
624
+ out += text.slice(last, at) + PPK_CONTAINER_TOKEN;
625
+ last = end;
626
+ count += 1;
627
+ at = end >= text.length ? Number.POSITIVE_INFINITY : nextVersionedHead(end);
628
+ }
629
+ return count === 0 ? { out: text, count: 0 } : { out: out + text.slice(last), count };
630
+ }
631
+ const urlUserinfoRule = { label: labelOfToken(URL_USERINFO_PATTERN[1]), arm: "full", run: (text, _wantCount, json) => redactUrlUserinfo(text, json) };
632
+ const SECRET_RULES = [
633
+ ...FULL_SECRET_PATTERNS.map((p) => (p === URL_USERINFO_PATTERN ? urlUserinfoRule : regexRule(p, "full"))),
634
+ { label: labelOfToken(PPK_CONTAINER_TOKEN), arm: "full", run: (text, _wantCount, _json, observe) => redactPpkContainers(text, observe) },
635
+ { label: labelOfToken(PEM_FRAGMENT_TOKEN), arm: "fragment", run: (text, _wantCount, json) => redactPemFragments(text, json) },
636
+ { label: labelOfToken(HEADLESS_PPK_TOKEN), arm: "fragment", run: (text) => redactHeadlessPpkBodies(text) },
637
+ ...FRAGMENT_SECRET_PATTERNS.map((p) => regexRule(p, "fragment")),
638
+ { ...urlUserinfoRule, run: (text, _wantCount, json) => ({ out: redactUrlUserinfo(text, json).out, count: 0 }) },
639
+ ];
13
640
  let onRedaction;
14
641
  export function setRedactionObserver(fn) {
15
642
  onRedaction = fn;
16
643
  }
17
- const PATTERN_LABELS = SECRET_PATTERNS.map(([, repl]) => repl.replace(/[«»]/g, "").replace(/^redacted:/, ""));
18
- export function redactSecrets(text, opts) {
19
- const observe = opts?.observe ?? true;
644
+ function applyRules(text, wantCount, json, observe) {
20
645
  let out = text;
21
- for (let i = 0; i < SECRET_PATTERNS.length; i++) {
22
- const [re, repl] = SECRET_PATTERNS[i];
23
- if (!onRedaction || !observe) {
24
- out = out.replace(re, repl);
25
- continue;
646
+ const hits = [];
647
+ for (const rule of SECRET_RULES) {
648
+ const r = rule.run(out, wantCount, json, observe);
649
+ out = r.out;
650
+ if (wantCount && r.count > 0)
651
+ hits.push({ label: rule.label, count: r.count, arm: rule.arm });
652
+ }
653
+ return { out, hits };
654
+ }
655
+ function reportHits(hits) {
656
+ if (onRedaction === undefined)
657
+ return;
658
+ for (const h of hits) {
659
+ try {
660
+ onRedaction(h.label, h.count, { arm: h.arm });
26
661
  }
27
- const n = out.match(re)?.length ?? 0;
28
- out = out.replace(re, repl);
29
- if (n > 0) {
30
- try {
31
- onRedaction(PATTERN_LABELS[i], n);
32
- }
33
- catch {
34
- }
662
+ catch {
35
663
  }
36
664
  }
37
- return out;
665
+ }
666
+ export function redactSecrets(text, opts) {
667
+ const wantCount = onRedaction !== undefined && (opts?.observe ?? true);
668
+ const json = opts?.json === true;
669
+ let r;
670
+ r = applyRules(text, wantCount, json, opts?.observe ?? true);
671
+ reportHits(r.hits);
672
+ return r.out;
38
673
  }
39
674
  const REDACT_MAX_DEPTH = 64;
40
675
  export function redactDeep(value) {
@@ -91,7 +726,7 @@ export function redactedPreview(value, cap = 2000) {
91
726
  }
92
727
  if (s.length === 0)
93
728
  return null;
94
- s = redactSecrets(s);
729
+ s = redactSecrets(s, { json: true });
95
730
  return s.length > cap ? `${s.slice(0, cap)}…[truncated]` : s;
96
731
  }
97
732
  //# sourceMappingURL=redact.js.map