token-goat 2.8.1 → 2.8.3
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/README.md +15 -9
- package/dist/{token-goat-chunk-VBXBLGTO.mjs → token-goat-chunk-222VPFP2.mjs} +147 -53
- package/dist/{token-goat-chunk-XEDQH5DA.mjs → token-goat-chunk-4HIMCBYK.mjs} +6 -6
- package/dist/{token-goat-chunk-Y2SYNH3S.mjs → token-goat-chunk-4OM2Q2SX.mjs} +5 -5
- package/dist/{token-goat-chunk-ELDJRLHZ.mjs → token-goat-chunk-6ODZ6PZK.mjs} +219 -147
- package/dist/{token-goat-chunk-73MF6YWW.mjs → token-goat-chunk-C5IL6MJH.mjs} +3 -3
- package/dist/{token-goat-chunk-YUGNM3KL.mjs → token-goat-chunk-DP3NNGGV.mjs} +5 -5
- package/dist/{token-goat-chunk-7JDXDERZ.mjs → token-goat-chunk-EFF2XCLB.mjs} +31 -18
- package/dist/{token-goat-chunk-AOBZUFNJ.mjs → token-goat-chunk-NKNCHJ4H.mjs} +80 -49
- package/dist/{token-goat-chunk-2G6RAB4G.mjs → token-goat-chunk-PWVXXPCC.mjs} +136 -31
- package/dist/{token-goat-chunk-RMDQFTQD.mjs → token-goat-chunk-TX4JFJTD.mjs} +76 -19
- package/dist/{token-goat-chunk-I6TOUPLP.mjs → token-goat-chunk-VAGPOZHO.mjs} +2 -2
- package/dist/token-goat-hook.mjs +5 -5
- package/dist/token-goat.core.mjs +5 -5
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
9
|
function resolveVersion() {
|
|
10
10
|
if (true) {
|
|
11
|
-
return "2.8.
|
|
11
|
+
return "2.8.3";
|
|
12
12
|
}
|
|
13
13
|
const require2 = createRequire(import.meta.url);
|
|
14
14
|
const pkg = require2("../package.json");
|
|
@@ -3787,7 +3787,12 @@ function lineCommentStartIndex(line, markers, from = 0) {
|
|
|
3787
3787
|
}
|
|
3788
3788
|
return cutIdx;
|
|
3789
3789
|
}
|
|
3790
|
-
function stripStringLiterals(line) {
|
|
3790
|
+
function stripStringLiterals(line, opts = {}) {
|
|
3791
|
+
const openDelim = (idx) => {
|
|
3792
|
+
const q = line[idx];
|
|
3793
|
+
if (opts.tripleQuotes && line[idx + 1] === q && line[idx + 2] === q) return q + q + q;
|
|
3794
|
+
return q;
|
|
3795
|
+
};
|
|
3791
3796
|
let out = "";
|
|
3792
3797
|
let i = 0;
|
|
3793
3798
|
const stack = [];
|
|
@@ -3803,9 +3808,10 @@ function stripStringLiterals(line) {
|
|
|
3803
3808
|
if (top === void 0) {
|
|
3804
3809
|
if (ch === '"' || ch === "'") {
|
|
3805
3810
|
const bareBraceHole = ch === '"' && i > 0 && line[i - 1] === "$";
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3811
|
+
const delim = openDelim(i);
|
|
3812
|
+
stack.push({ kind: "string", quote: delim, bareBraceHole });
|
|
3813
|
+
out += delim;
|
|
3814
|
+
i += delim.length;
|
|
3809
3815
|
continue;
|
|
3810
3816
|
}
|
|
3811
3817
|
out += ch;
|
|
@@ -3815,9 +3821,10 @@ function stripStringLiterals(line) {
|
|
|
3815
3821
|
if (top.kind === "hole") {
|
|
3816
3822
|
if (ch === '"' || ch === "'") {
|
|
3817
3823
|
const bareBraceHole = ch === '"' && i > 0 && line[i - 1] === "$";
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3824
|
+
const delim = openDelim(i);
|
|
3825
|
+
stack.push({ kind: "string", quote: delim, bareBraceHole });
|
|
3826
|
+
out += delim;
|
|
3827
|
+
i += delim.length;
|
|
3821
3828
|
continue;
|
|
3822
3829
|
}
|
|
3823
3830
|
if (ch === "{") {
|
|
@@ -3845,10 +3852,10 @@ function stripStringLiterals(line) {
|
|
|
3845
3852
|
i += 2;
|
|
3846
3853
|
continue;
|
|
3847
3854
|
}
|
|
3848
|
-
if (ch === top.quote) {
|
|
3855
|
+
if (ch === top.quote[0] && (top.quote.length === 1 || line.startsWith(top.quote, i))) {
|
|
3849
3856
|
stack.pop();
|
|
3850
|
-
out +=
|
|
3851
|
-
i
|
|
3857
|
+
out += top.quote;
|
|
3858
|
+
i += top.quote.length;
|
|
3852
3859
|
continue;
|
|
3853
3860
|
}
|
|
3854
3861
|
if (top.quote === '"') {
|
|
@@ -4192,14 +4199,95 @@ function propagateEndLinesToSymbols(symbols, sections) {
|
|
|
4192
4199
|
return sym;
|
|
4193
4200
|
});
|
|
4194
4201
|
}
|
|
4202
|
+
function tripleQuoteDelimiters(opts) {
|
|
4203
|
+
const delims = [];
|
|
4204
|
+
if (opts?.tripleQuote === true) delims.push('"""');
|
|
4205
|
+
if (opts?.tripleSingleQuote === true) delims.push("'''");
|
|
4206
|
+
return delims;
|
|
4207
|
+
}
|
|
4208
|
+
function quoteRunLength(content, i) {
|
|
4209
|
+
let n = 0;
|
|
4210
|
+
while (content[i + n] === '"') n++;
|
|
4211
|
+
return n;
|
|
4212
|
+
}
|
|
4213
|
+
function skipRawStringQuotes(content, from, min) {
|
|
4214
|
+
for (let i = from; i < content.length; i++) {
|
|
4215
|
+
if (content[i] !== '"') continue;
|
|
4216
|
+
const run = quoteRunLength(content, i);
|
|
4217
|
+
if (run >= min) return i + run;
|
|
4218
|
+
i += run - 1;
|
|
4219
|
+
}
|
|
4220
|
+
return -1;
|
|
4221
|
+
}
|
|
4222
|
+
function stepPowershellString(content, i, quote) {
|
|
4223
|
+
const ch = content[i];
|
|
4224
|
+
if (quote === '"' && ch === "`") return { next: i + 1, open: true };
|
|
4225
|
+
return { next: i, open: ch !== quote };
|
|
4226
|
+
}
|
|
4227
|
+
function opensCsharpVerbatimString(content, i) {
|
|
4228
|
+
const prev = content[i - 1];
|
|
4229
|
+
return prev === "@" || prev === "$" && content[i - 2] === "@";
|
|
4230
|
+
}
|
|
4231
|
+
function toLineCommentPrefixes(prefix) {
|
|
4232
|
+
if (prefix === void 0) return [];
|
|
4233
|
+
return typeof prefix === "string" ? [prefix] : prefix;
|
|
4234
|
+
}
|
|
4235
|
+
function atLineComment(content, i, prefixes, exceptions) {
|
|
4236
|
+
if (exceptions.some((e) => content.startsWith(e, i))) return false;
|
|
4237
|
+
return prefixes.some((prefix) => content.startsWith(prefix, i));
|
|
4238
|
+
}
|
|
4239
|
+
function skipBlockComment(content, start, block, nested) {
|
|
4240
|
+
let i = start + block[0].length;
|
|
4241
|
+
let depth = 1;
|
|
4242
|
+
while (i < content.length) {
|
|
4243
|
+
if (nested && content.startsWith(block[0], i)) {
|
|
4244
|
+
depth++;
|
|
4245
|
+
i += block[0].length;
|
|
4246
|
+
continue;
|
|
4247
|
+
}
|
|
4248
|
+
if (content.startsWith(block[1], i)) {
|
|
4249
|
+
depth--;
|
|
4250
|
+
i += block[1].length;
|
|
4251
|
+
if (depth === 0) return i;
|
|
4252
|
+
continue;
|
|
4253
|
+
}
|
|
4254
|
+
i++;
|
|
4255
|
+
}
|
|
4256
|
+
return -1;
|
|
4257
|
+
}
|
|
4195
4258
|
function findMatchingBraceEndLine(content, openBraceIndex, totalLines, lineIndex, lineCommentPrefix, opts) {
|
|
4259
|
+
const linePrefixes = toLineCommentPrefixes(lineCommentPrefix);
|
|
4260
|
+
const lineExceptions = opts?.lineCommentExceptions ?? [];
|
|
4196
4261
|
const block = opts?.blockComment;
|
|
4197
4262
|
const backtick = opts?.backtickQuote === true;
|
|
4263
|
+
const escapes = opts?.stringEscapes ?? "backslash";
|
|
4264
|
+
const nestedBlock = opts?.nestedBlockComments === true;
|
|
4265
|
+
const rawString = opts?.rawStringQuotes === true;
|
|
4266
|
+
const tripleDelims = tripleQuoteDelimiters(opts);
|
|
4267
|
+
const lineString = opts?.lineStringPrefix;
|
|
4198
4268
|
let depth = 0;
|
|
4199
4269
|
let quote = null;
|
|
4270
|
+
let verbatim = false;
|
|
4200
4271
|
for (let i = openBraceIndex; i < content.length; i++) {
|
|
4201
4272
|
const ch = content[i];
|
|
4202
4273
|
if (quote !== null) {
|
|
4274
|
+
if (verbatim) {
|
|
4275
|
+
if (ch === quote) {
|
|
4276
|
+
if (content[i + 1] === quote) {
|
|
4277
|
+
i++;
|
|
4278
|
+
continue;
|
|
4279
|
+
}
|
|
4280
|
+
quote = null;
|
|
4281
|
+
verbatim = false;
|
|
4282
|
+
}
|
|
4283
|
+
continue;
|
|
4284
|
+
}
|
|
4285
|
+
if (escapes === "powershell") {
|
|
4286
|
+
const step = stepPowershellString(content, i, quote);
|
|
4287
|
+
i = step.next;
|
|
4288
|
+
if (!step.open) quote = null;
|
|
4289
|
+
continue;
|
|
4290
|
+
}
|
|
4203
4291
|
if (ch === "\\") {
|
|
4204
4292
|
i++;
|
|
4205
4293
|
continue;
|
|
@@ -4208,16 +4296,35 @@ function findMatchingBraceEndLine(content, openBraceIndex, totalLines, lineIndex
|
|
|
4208
4296
|
continue;
|
|
4209
4297
|
}
|
|
4210
4298
|
if (block !== void 0 && content.startsWith(block[0], i)) {
|
|
4211
|
-
const
|
|
4212
|
-
i =
|
|
4299
|
+
const end = skipBlockComment(content, i, block, nestedBlock);
|
|
4300
|
+
i = end === -1 ? content.length : end - 1;
|
|
4213
4301
|
continue;
|
|
4214
4302
|
}
|
|
4215
|
-
if (
|
|
4303
|
+
if (atLineComment(content, i, linePrefixes, lineExceptions)) {
|
|
4216
4304
|
while (i < content.length && content[i] !== "\n") i++;
|
|
4217
4305
|
continue;
|
|
4218
4306
|
}
|
|
4307
|
+
if (lineString !== void 0 && content.startsWith(lineString, i)) {
|
|
4308
|
+
while (i < content.length && content[i] !== "\n") i++;
|
|
4309
|
+
continue;
|
|
4310
|
+
}
|
|
4311
|
+
if (rawString && ch === '"') {
|
|
4312
|
+
const run = quoteRunLength(content, i);
|
|
4313
|
+
if (run >= 3) {
|
|
4314
|
+
const end = skipRawStringQuotes(content, i + run, run);
|
|
4315
|
+
i = end === -1 ? content.length : end - 1;
|
|
4316
|
+
continue;
|
|
4317
|
+
}
|
|
4318
|
+
}
|
|
4319
|
+
const tripleAt = tripleDelims.find((t) => content.startsWith(t, i));
|
|
4320
|
+
if (tripleAt !== void 0) {
|
|
4321
|
+
const end = content.indexOf(tripleAt, i + 3);
|
|
4322
|
+
i = end === -1 ? content.length : end + 2;
|
|
4323
|
+
continue;
|
|
4324
|
+
}
|
|
4219
4325
|
if (ch === '"' || ch === "'" || backtick && ch === "`") {
|
|
4220
4326
|
quote = ch;
|
|
4327
|
+
verbatim = escapes === "csharp" && ch === '"' && opensCsharpVerbatimString(content, i);
|
|
4221
4328
|
continue;
|
|
4222
4329
|
}
|
|
4223
4330
|
if (ch === "{") depth++;
|
|
@@ -4230,18 +4337,34 @@ function findMatchingBraceEndLine(content, openBraceIndex, totalLines, lineIndex
|
|
|
4230
4337
|
}
|
|
4231
4338
|
return opts?.noMatchValue ?? totalLines;
|
|
4232
4339
|
}
|
|
4233
|
-
function assignBraceBlockSpans(symbols, content,
|
|
4340
|
+
function assignBraceBlockSpans(symbols, content, opts = {}) {
|
|
4341
|
+
const lineCommentPrefix = opts.lineComment;
|
|
4342
|
+
const stringEscapes = opts.stringEscapes ?? "backslash";
|
|
4343
|
+
const nestedBlockComments = opts.nestedBlockComments ?? false;
|
|
4344
|
+
const tripleQuote = opts.tripleQuote ?? false;
|
|
4345
|
+
const lineStringPrefix = opts.lineStringPrefix;
|
|
4346
|
+
const firstLinePrefix = toLineCommentPrefixes(lineCommentPrefix)[0];
|
|
4234
4347
|
if (symbols.length === 0) return [...symbols];
|
|
4235
4348
|
const lines = content.split("\n");
|
|
4236
4349
|
const totalLines = lines.length;
|
|
4237
4350
|
const lineIndex = buildLineIndex(content);
|
|
4238
4351
|
const starts = [...new Set(symbols.map((s) => s.lineStart))].sort((a, b) => a - b);
|
|
4239
|
-
const blockComment =
|
|
4352
|
+
const blockComment = opts.blockComment !== void 0 ? opts.blockComment ?? void 0 : firstLinePrefix === "//" ? ["/*", "*/"] : firstLinePrefix === "#" ? ["<#", "#>"] : void 0;
|
|
4353
|
+
const scanOpts = {
|
|
4354
|
+
noMatchValue: -1,
|
|
4355
|
+
stringEscapes,
|
|
4356
|
+
tripleQuote,
|
|
4357
|
+
rawStringQuotes: opts.rawStringQuotes ?? false,
|
|
4358
|
+
tripleSingleQuote: opts.tripleSingleQuote ?? false,
|
|
4359
|
+
...blockComment === void 0 ? {} : { blockComment, nestedBlockComments },
|
|
4360
|
+
...lineStringPrefix === void 0 ? {} : { lineStringPrefix },
|
|
4361
|
+
...opts.lineCommentExceptions === void 0 ? {} : { lineCommentExceptions: opts.lineCommentExceptions }
|
|
4362
|
+
};
|
|
4240
4363
|
return symbols.map((sym) => {
|
|
4241
4364
|
if (sym.lineEnd !== sym.lineStart) return sym;
|
|
4242
4365
|
const nextStart = starts.find((s) => s > sym.lineStart);
|
|
4243
4366
|
const lastSearchLine = Math.min(nextStart !== void 0 ? nextStart - 1 : totalLines, sym.lineStart + BRACE_SEARCH_MAX_LINES);
|
|
4244
|
-
const openIndex = findBlockOpenBrace(content, lineIndex, sym.lineStart, lastSearchLine, lineCommentPrefix,
|
|
4367
|
+
const openIndex = findBlockOpenBrace(content, lineIndex, sym.lineStart, lastSearchLine, lineCommentPrefix, scanOpts);
|
|
4245
4368
|
if (openIndex === null) return sym;
|
|
4246
4369
|
const endLine = findMatchingBraceEndLine(
|
|
4247
4370
|
content,
|
|
@@ -4249,14 +4372,22 @@ function assignBraceBlockSpans(symbols, content, lineCommentPrefix) {
|
|
|
4249
4372
|
totalLines,
|
|
4250
4373
|
lineIndex,
|
|
4251
4374
|
lineCommentPrefix,
|
|
4252
|
-
|
|
4375
|
+
scanOpts
|
|
4253
4376
|
);
|
|
4254
4377
|
if (endLine <= sym.lineStart) return sym;
|
|
4255
4378
|
return { ...sym, lineEnd: endLine, body: lines.slice(sym.lineStart - 1, endLine).join("\n") };
|
|
4256
4379
|
});
|
|
4257
4380
|
}
|
|
4258
4381
|
var BRACE_SEARCH_MAX_LINES = 10;
|
|
4259
|
-
function findBlockOpenBrace(content, lineIndex, startLine, lastSearchLine, lineCommentPrefix,
|
|
4382
|
+
function findBlockOpenBrace(content, lineIndex, startLine, lastSearchLine, lineCommentPrefix, opts) {
|
|
4383
|
+
const blockComment = opts?.blockComment;
|
|
4384
|
+
const stringEscapes = opts?.stringEscapes ?? "backslash";
|
|
4385
|
+
const nestedBlockComments = opts?.nestedBlockComments === true;
|
|
4386
|
+
const rawString = opts?.rawStringQuotes === true;
|
|
4387
|
+
const tripleDelims = tripleQuoteDelimiters(opts);
|
|
4388
|
+
const lineString = opts?.lineStringPrefix;
|
|
4389
|
+
const linePrefixes = toLineCommentPrefixes(lineCommentPrefix);
|
|
4390
|
+
const lineExceptions = opts?.lineCommentExceptions ?? [];
|
|
4260
4391
|
const from = lineIndex[startLine - 1];
|
|
4261
4392
|
if (from === void 0) return null;
|
|
4262
4393
|
const to = lineIndex[lastSearchLine] ?? content.length;
|
|
@@ -4264,10 +4395,28 @@ function findBlockOpenBrace(content, lineIndex, startLine, lastSearchLine, lineC
|
|
|
4264
4395
|
let parenDepth = 0;
|
|
4265
4396
|
let linesSeen = 0;
|
|
4266
4397
|
let atLineStart = false;
|
|
4398
|
+
let verbatim = false;
|
|
4267
4399
|
for (let i = from; i < to; i++) {
|
|
4268
4400
|
const ch = content[i];
|
|
4269
4401
|
if (ch === void 0) break;
|
|
4270
4402
|
if (quote !== null) {
|
|
4403
|
+
if (verbatim) {
|
|
4404
|
+
if (ch === quote) {
|
|
4405
|
+
if (content[i + 1] === quote) {
|
|
4406
|
+
i++;
|
|
4407
|
+
continue;
|
|
4408
|
+
}
|
|
4409
|
+
quote = null;
|
|
4410
|
+
verbatim = false;
|
|
4411
|
+
}
|
|
4412
|
+
continue;
|
|
4413
|
+
}
|
|
4414
|
+
if (stringEscapes === "powershell") {
|
|
4415
|
+
const step = stepPowershellString(content, i, quote);
|
|
4416
|
+
i = step.next;
|
|
4417
|
+
if (!step.open) quote = null;
|
|
4418
|
+
continue;
|
|
4419
|
+
}
|
|
4271
4420
|
if (ch === "\\") {
|
|
4272
4421
|
i++;
|
|
4273
4422
|
continue;
|
|
@@ -4281,12 +4430,12 @@ function findBlockOpenBrace(content, lineIndex, startLine, lastSearchLine, lineC
|
|
|
4281
4430
|
continue;
|
|
4282
4431
|
}
|
|
4283
4432
|
if (blockComment !== void 0 && content.startsWith(blockComment[0], i)) {
|
|
4284
|
-
const
|
|
4285
|
-
if (
|
|
4286
|
-
i =
|
|
4433
|
+
const end = skipBlockComment(content, i, blockComment, nestedBlockComments);
|
|
4434
|
+
if (end === -1) return null;
|
|
4435
|
+
i = end - 1;
|
|
4287
4436
|
continue;
|
|
4288
4437
|
}
|
|
4289
|
-
if (
|
|
4438
|
+
if (atLineComment(content, i, linePrefixes, lineExceptions)) {
|
|
4290
4439
|
while (i + 1 < to && content[i + 1] !== "\n") i++;
|
|
4291
4440
|
continue;
|
|
4292
4441
|
}
|
|
@@ -4294,8 +4443,29 @@ function findBlockOpenBrace(content, lineIndex, startLine, lastSearchLine, lineC
|
|
|
4294
4443
|
atLineStart = false;
|
|
4295
4444
|
if (parenDepth === 0 && linesSeen >= 1 && startsWithBlockKeyword(content, i, to)) return null;
|
|
4296
4445
|
}
|
|
4446
|
+
if (lineString !== void 0 && content.startsWith(lineString, i)) {
|
|
4447
|
+
while (i + 1 < to && content[i + 1] !== "\n") i++;
|
|
4448
|
+
continue;
|
|
4449
|
+
}
|
|
4450
|
+
if (rawString && ch === '"') {
|
|
4451
|
+
const run = quoteRunLength(content, i);
|
|
4452
|
+
if (run >= 3) {
|
|
4453
|
+
const end = skipRawStringQuotes(content, i + run, run);
|
|
4454
|
+
if (end === -1) return null;
|
|
4455
|
+
i = end - 1;
|
|
4456
|
+
continue;
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
const tripleAt = tripleDelims.find((t) => content.startsWith(t, i));
|
|
4460
|
+
if (tripleAt !== void 0) {
|
|
4461
|
+
const end = content.indexOf(tripleAt, i + 3);
|
|
4462
|
+
if (end === -1) return null;
|
|
4463
|
+
i = end + 2;
|
|
4464
|
+
continue;
|
|
4465
|
+
}
|
|
4297
4466
|
if (ch === '"' || ch === "'") {
|
|
4298
4467
|
quote = ch;
|
|
4468
|
+
verbatim = stringEscapes === "csharp" && ch === '"' && opensCsharpVerbatimString(content, i);
|
|
4299
4469
|
continue;
|
|
4300
4470
|
}
|
|
4301
4471
|
if (ch === "(" || ch === "[") parenDepth++;
|
|
@@ -6057,6 +6227,10 @@ var KIND_TO_SOURCE = {
|
|
|
6057
6227
|
// Cold first load of an oversized skill where preSkillHandler inlined the compact slice in its reply instead of pointing at `skill-body --compact`. Unlike its skill_oversized_first_load sibling (event-only, 0 bytes -- the pointer deny saves nothing by itself, the follow-up command does) this one records real savings: the full body never landed, the slice did, so bytesSaved is body minus slice.
|
|
6058
6228
|
skill_compact_inlined: SOURCE_SKILL,
|
|
6059
6229
|
secret_redacted: SOURCE_OTHER,
|
|
6230
|
+
// Fail-soft diagnostic counters from hooks_edit.ts: they record that a side task threw, never a byte saving, so "other" is the right home. Listed explicitly rather than left to kindToSource()'s fallback so the registration guard can tell a deliberate placement from an unregistered kind.
|
|
6231
|
+
dirty_queue_append_failed: SOURCE_OTHER,
|
|
6232
|
+
worker_healthcheck_failed: SOURCE_OTHER,
|
|
6233
|
+
known_root_record_failed: SOURCE_OTHER,
|
|
6060
6234
|
// Measurement of what a compaction produced (hooks_compact.ts postCompactHandler): summary size and how many manifest paths survived into it. SOURCE_OTHER and always recorded at (0, 0) -- the summary was written whether or not token-goat was watching, so there is no counterfactual in which those bytes were saved. Filing it anywhere with a savings total would credit token-goat for the whole summary, which is the accounting mistake this registry exists to prevent.
|
|
6061
6235
|
compact_summary: SOURCE_OTHER,
|
|
6062
6236
|
// Envelope compaction of an oversized subagent report (hooks_agent_spawn.ts). SOURCE_CONTENT, not SOURCE_HINT: the handler's sibling session_hint entry is advisory (it only appends a recall pointer and genuinely saves nothing), whereas this kind records a real rewrite with real bytes removed, so filing it under the advisory bucket would understate the compaction and repeat the zero-savings desync this registry keeps getting bitten by.
|
|
@@ -6076,7 +6250,9 @@ var KIND_PREFIX_TO_SOURCE = [
|
|
|
6076
6250
|
["gdrive:", SOURCE_WEB],
|
|
6077
6251
|
["mcp:", SOURCE_MCP],
|
|
6078
6252
|
["skill_body:", SOURCE_SKILL],
|
|
6079
|
-
["skill_compact:", SOURCE_SKILL]
|
|
6253
|
+
["skill_compact:", SOURCE_SKILL],
|
|
6254
|
+
["bashoutput:", SOURCE_BASH],
|
|
6255
|
+
["taskoutput:", SOURCE_CONTENT]
|
|
6080
6256
|
];
|
|
6081
6257
|
var COMMAND_KINDS = {
|
|
6082
6258
|
symbol: /* @__PURE__ */ new Set(["symbol_lookup"]),
|
|
@@ -6529,150 +6705,45 @@ function renderStats2(opts) {
|
|
|
6529
6705
|
|
|
6530
6706
|
// src/secret_redact.ts
|
|
6531
6707
|
var SECRET_PATTERNS = [
|
|
6532
|
-
// Anthropic keys share OpenAI's "sk-" prefix but are more specific
|
|
6533
|
-
// ("sk-ant-"), so they're matched first — the generic OpenAI pattern's
|
|
6534
|
-
// negative lookahead below is defense in depth, not the sole guard.
|
|
6708
|
+
// Anthropic keys share OpenAI's "sk-" prefix but are more specific ("sk-ant-"), so they're matched first — the generic OpenAI pattern's negative lookahead below is defense in depth, not the sole guard.
|
|
6535
6709
|
["anthropic_api_key", /sk-ant-[A-Za-z0-9_-]{20,}/g],
|
|
6536
|
-
// Modern (post-2024, now the default) OpenAI project keys: "sk-proj-" followed by a long
|
|
6537
|
-
// base64url-ish body that legitimately contains '-' and '_' -- the generic pattern below
|
|
6538
|
-
// deliberately excludes those characters (hyphenated prose would false-fire), so this needs
|
|
6539
|
-
// its own entry with the more specific prefix matched first, like sk-ant- above.
|
|
6710
|
+
// Modern (post-2024, now the default) OpenAI project keys: "sk-proj-" followed by a long base64url-ish body that legitimately contains '-' and '_' -- the generic pattern below deliberately excludes those characters (hyphenated prose would false-fire), so this needs its own entry with the more specific prefix matched first, like sk-ant- above.
|
|
6540
6711
|
["openai_project_key", /sk-proj-[A-Za-z0-9_-]{20,}/g],
|
|
6541
6712
|
["openai_api_key", /sk-(?!ant-|proj-)[A-Za-z0-9]{20,}/g],
|
|
6542
6713
|
["aws_access_key", /AKIA[0-9A-Z]{16}/g],
|
|
6543
|
-
// Fine-grained PATs ("github_pat_...") are matched before the classic gh[oprsu]_ pattern so
|
|
6544
|
-
// the full token is always consumed as one match -- a fine-grained token's own body can
|
|
6545
|
-
// contain '_' and could otherwise partially match the classic pattern.
|
|
6714
|
+
// Fine-grained PATs ("github_pat_...") are matched before the classic gh[oprsu]_ pattern so the full token is always consumed as one match -- a fine-grained token's own body can contain '_' and could otherwise partially match the classic pattern.
|
|
6546
6715
|
["github_token", /github_pat_[A-Za-z0-9_]{22,}/g],
|
|
6547
6716
|
["github_token", /gh[oprsu]_[A-Za-z0-9]{36,}/g],
|
|
6548
|
-
// xapp- is Slack's app-level token (Socket Mode), a bearer credential in its own right that
|
|
6549
|
-
// the xox[baprs]- prefix does not cover. It gets its own entry rather than joining the
|
|
6550
|
-
// alternation above because it needs the full segmented shape (xapp-<ver>-<app id>-<digits>-
|
|
6551
|
-
// <hex>) to be safe: a bare /xapp-[A-Za-z0-9-]+/ redacted ordinary identifiers like
|
|
6552
|
-
// "xapp-config" and the css class "xapp-container", mangling normal source. The xox* prefixes
|
|
6553
|
-
// are distinctive enough on their own; "xapp-" is not.
|
|
6717
|
+
// xapp- is Slack's app-level token (Socket Mode), a bearer credential in its own right that the xox[baprs]- prefix does not cover. It gets its own entry rather than joining the alternation above because it needs the full segmented shape (xapp-<ver>-<app id>-<digits>- <hex>) to be safe: a bare /xapp-[A-Za-z0-9-]+/ redacted ordinary identifiers like "xapp-config" and the css class "xapp-container", mangling normal source. The xox* prefixes are distinctive enough on their own; "xapp-" is not.
|
|
6554
6718
|
["slack_token", /xox[baprs]-[A-Za-z0-9-]+/g],
|
|
6555
6719
|
["slack_token", /xapp-\d-[A-Za-z0-9]{6,}-\d{8,}-[A-Za-z0-9]{16,}/g],
|
|
6556
|
-
// Matches the full block (BEGIN marker through its matching END marker), not just the
|
|
6557
|
-
// header -- the actual secret material is the base64 body between them, so redacting only
|
|
6558
|
-
// the header line would leave the key bytes themselves fully readable in the cached blob.
|
|
6559
|
-
// The body is lazy and additionally refuses to cross a following BEGIN marker. Both halves
|
|
6560
|
-
// matter. The laziness bounds a successful match at the very next END marker; the negative
|
|
6561
|
-
// lookahead bounds a *failing* one, because a BEGIN with no END of its own would otherwise
|
|
6562
|
-
// scan to end-of-input, and a blob full of such markers made the pass quadratic -- 6 ms, 20 ms
|
|
6563
|
-
// and 78 ms for 2000, 4000 and 8000 of them, four times the work for twice the input, the same
|
|
6564
|
-
// shape as the lookbehind incident described above. Private key blocks do not nest, so
|
|
6565
|
-
// refusing to cross a BEGIN costs nothing in correctness.
|
|
6566
|
-
// The algorithm and ` BLOCK` groups are backreferenced in the END marker rather than repeated,
|
|
6567
|
-
// so a BEGIN only ever pairs with its own END spelling. Written as two independent optional
|
|
6568
|
-
// groups, `BEGIN PGP PRIVATE KEY` would happily close on a distant `END RSA PRIVATE KEY BLOCK`
|
|
6569
|
-
// and redact everything in between. A non-participating group backreferences as the empty
|
|
6570
|
-
// string in JS, which is exactly what the unprefixed `BEGIN PRIVATE KEY` form needs.
|
|
6571
|
-
// The algorithm list covers every armored private-key header openssl, ssh-keygen and gpg
|
|
6572
|
-
// actually emit, not just the ones a first draft happened to think of. ENCRYPTED is the
|
|
6573
|
-
// PKCS#8 passphrase-protected form (`openssl genpkey -aes256`, `ssh-keygen -m PKCS8`) and is
|
|
6574
|
-
// the most common shape of all; DSA is legacy but still written verbatim; PGP carries the
|
|
6575
|
-
// ` BLOCK` suffix, which is why that suffix is optional here. All three used to fall through
|
|
6576
|
-
// to disk in full. An encrypted key is still key material: the passphrase can be attacked
|
|
6577
|
-
// offline once the bytes are cached, so it is redacted like any other.
|
|
6578
|
-
// PuTTY `.ppk` files are deliberately not matched. They have no END marker -- the private
|
|
6579
|
-
// section is a `Private-Lines: N` count followed by exactly N base64 lines -- so bounding a
|
|
6580
|
-
// match would take a stateful parse rather than a regex, on a path that runs over every
|
|
6581
|
-
// command output. A regex guess at where the body ends is exactly the over-eager match this
|
|
6582
|
-
// module's header warns against.
|
|
6720
|
+
// Matches the full block (BEGIN marker through its matching END marker), not just the header -- the actual secret material is the base64 body between them, so redacting only the header line would leave the key bytes themselves fully readable in the cached blob. The body is lazy and additionally refuses to cross a following BEGIN marker. Both halves matter. The laziness bounds a successful match at the very next END marker; the negative lookahead bounds a *failing* one, because a BEGIN with no END of its own would otherwise scan to end-of-input, and a blob full of such markers made the pass quadratic -- 6 ms, 20 ms and 78 ms for 2000, 4000 and 8000 of them, four times the work for twice the input, the same shape as the lookbehind incident described above. Private key blocks do not nest, so refusing to cross a BEGIN costs nothing in correctness. The algorithm and ` BLOCK` groups are backreferenced in the END marker rather than repeated, so a BEGIN only ever pairs with its own END spelling. Written as two independent optional groups, `BEGIN PGP PRIVATE KEY` would happily close on a distant `END RSA PRIVATE KEY BLOCK` and redact everything in between. A non-participating group backreferences as the empty string in JS, which is exactly what the unprefixed `BEGIN PRIVATE KEY` form needs. The algorithm list covers every armored private-key header openssl, ssh-keygen and gpg actually emit, not just the ones a first draft happened to think of. ENCRYPTED is the PKCS#8 passphrase-protected form (`openssl genpkey -aes256`, `ssh-keygen -m PKCS8`) and is the most common shape of all; DSA is legacy but still written verbatim; PGP carries the ` BLOCK` suffix, which is why that suffix is optional here. All three used to fall through to disk in full. An encrypted key is still key material: the passphrase can be attacked offline once the bytes are cached, so it is redacted like any other. PuTTY `.ppk` files are deliberately not matched. They have no END marker -- the private section is a `Private-Lines: N` count followed by exactly N base64 lines -- so bounding a match would take a stateful parse rather than a regex, on a path that runs over every command output. A regex guess at where the body ends is exactly the over-eager match this module's header warns against.
|
|
6583
6721
|
["private_key_block", /-----BEGIN (RSA |DSA |EC |OPENSSH |ENCRYPTED |PGP )?PRIVATE KEY( BLOCK)?-----(?:(?!-----BEGIN )[\s\S])*?-----END \1PRIVATE KEY\2-----/g],
|
|
6584
|
-
// Redacts only the token itself, not the "Authorization: Bearer " prefix -- the lookbehind
|
|
6585
|
-
// anchors on the header name and scheme so the surrounding request-log line stays readable,
|
|
6586
|
-
// matching how AWS_ACCESS_KEY_ID=... above keeps its own prefix intact.
|
|
6587
|
-
// The optional quotes on either side of the colon are what let this see a header carried in
|
|
6588
|
-
// JSON rather than in raw wire format. Without them the lookbehind demanded the colon sit
|
|
6589
|
-
// directly against the header name and the scheme directly against the space, so a body like
|
|
6590
|
-
// {"Authorization": "Bearer <token>"} -- the shape any logged fetch or MCP result arrives in
|
|
6591
|
-
// -- matched nothing and the token was cached verbatim.
|
|
6592
|
-
// `token` is the second scheme spelling in wide use -- it is what curl and gh examples pass for
|
|
6593
|
-
// GitHub and many other APIs -- and an opaque value behind it carries exactly the same authority
|
|
6594
|
-
// as one behind `Bearer`. The trailing gap is `{1,8}` rather than a single space for the same
|
|
6595
|
-
// reason every other gap in this lookbehind already is: a hand-aligned or reformatted header
|
|
6596
|
-
// ("Authorization: Bearer <token>") is ordinary, and demanding exactly one space there made
|
|
6597
|
-
// this the one position in the pattern that a second space defeated.
|
|
6722
|
+
// Redacts only the token itself, not the "Authorization: Bearer " prefix -- the lookbehind anchors on the header name and scheme so the surrounding request-log line stays readable, matching how AWS_ACCESS_KEY_ID=... above keeps its own prefix intact. The optional quotes on either side of the colon are what let this see a header carried in JSON rather than in raw wire format. Without them the lookbehind demanded the colon sit directly against the header name and the scheme directly against the space, so a body like {"Authorization": "Bearer <token>"} -- the shape any logged fetch or MCP result arrives in -- matched nothing and the token was cached verbatim. `token` is the second scheme spelling in wide use -- it is what curl and gh examples pass for GitHub and many other APIs -- and an opaque value behind it carries exactly the same authority as one behind `Bearer`. The trailing gap is `{1,8}` rather than a single space for the same reason every other gap in this lookbehind already is: a hand-aligned or reformatted header ("Authorization: Bearer <token>") is ordinary, and demanding exactly one space there made this the one position in the pattern that a second space defeated.
|
|
6598
6723
|
["auth_bearer_token", /(?<=Authorization["']?[ \t]{0,8}:[ \t]{0,8}["']?[ \t]{0,8}(?:Bearer|token)[ \t]{1,8})[A-Za-z0-9\-._~+/]{10,}=*/gi],
|
|
6599
6724
|
["auth_basic_token", /(?<=Authorization["']?[ \t]{0,8}:[ \t]{0,8}["']?[ \t]{0,8}Basic[ \t])[A-Za-z0-9+/]{6,}=*/gi],
|
|
6600
|
-
// JWTs have no distinctive prefix of their own, but the base64url encoding of the smallest
|
|
6601
|
-
// realistic header ('{"alg":' or similar) always starts with "eyJ", so that's the practical
|
|
6602
|
-
// anchor here -- each of the three dot-separated segments requires a minimum length to avoid
|
|
6603
|
-
// matching a short, coincidentally dotted token.
|
|
6725
|
+
// JWTs have no distinctive prefix of their own, but the base64url encoding of the smallest realistic header ('{"alg":' or similar) always starts with "eyJ", so that's the practical anchor here -- each of the three dot-separated segments requires a minimum length to avoid matching a short, coincidentally dotted token.
|
|
6604
6726
|
//
|
|
6605
|
-
// The trailing `={0,2}` on each segment is what makes a padded token match. Base64url as the JWT
|
|
6606
|
-
// spec defines it drops the `=` padding, but producers that reach for a plain base64 encoder
|
|
6607
|
-
// emit it anyway, and a `=` in the header or payload segment used to defeat the match outright:
|
|
6608
|
-
// not a partial redaction, but none at all, so the entire token was printed. `=` cannot appear
|
|
6609
|
-
// anywhere except the end of a segment, since it is not one of the characters the segment body
|
|
6610
|
-
// allows, so accepting it here cannot widen the match onto anything else.
|
|
6727
|
+
// The trailing `={0,2}` on each segment is what makes a padded token match. Base64url as the JWT spec defines it drops the `=` padding, but producers that reach for a plain base64 encoder emit it anyway, and a `=` in the header or payload segment used to defeat the match outright: not a partial redaction, but none at all, so the entire token was printed. `=` cannot appear anywhere except the end of a segment, since it is not one of the characters the segment body allows, so accepting it here cannot widen the match onto anything else.
|
|
6611
6728
|
["jwt", /eyJ[A-Za-z0-9_-]{10,}={0,2}\.[A-Za-z0-9_-]{10,}={0,2}\.[A-Za-z0-9_-]{10,}={0,2}/g],
|
|
6612
6729
|
["npm_token", /npm_[A-Za-z0-9]{36}/g],
|
|
6613
|
-
// rk_live_ (restricted keys) share the sk_live_/sk_test_ secret-key shape and risk level, so
|
|
6614
|
-
// one pattern covers all three rather than adding a near-duplicate entry.
|
|
6730
|
+
// rk_live_ (restricted keys) share the sk_live_/sk_test_ secret-key shape and risk level, so one pattern covers all three rather than adding a near-duplicate entry.
|
|
6615
6731
|
["stripe_key", /(?:sk_live_|sk_test_|rk_live_)[A-Za-z0-9]{20,}/g],
|
|
6616
6732
|
["google_api_key", /AIza[A-Za-z0-9_-]{35}/g],
|
|
6617
|
-
// Presigned-url signatures: AWS SigV4 (X-Amz-Signature), Google Cloud Storage
|
|
6618
|
-
// (X-Goog-Signature), and Azure blob SAS (sig). These are bearer credentials in query-string
|
|
6619
|
-
// clothing -- anyone holding the whole url can read or write the object until it expires, and
|
|
6620
|
-
// none of the prefix-anchored patterns above match them because the signature is a bare hex or
|
|
6621
|
-
// base64 blob with no distinctive prefix of its own. The '[?&]' anchor and the 16-char floor
|
|
6622
|
-
// are what make the short, generic 'sig' name safe to key on: a prose or code mention of "sig"
|
|
6623
|
-
// never sits directly after a query separator followed by that much opaque token.
|
|
6733
|
+
// Presigned-url signatures: AWS SigV4 (X-Amz-Signature), Google Cloud Storage (X-Goog-Signature), and Azure blob SAS (sig). These are bearer credentials in query-string clothing -- anyone holding the whole url can read or write the object until it expires, and none of the prefix-anchored patterns above match them because the signature is a bare hex or base64 blob with no distinctive prefix of its own. The '[?&]' anchor and the 16-char floor are what make the short, generic 'sig' name safe to key on: a prose or code mention of "sig" never sits directly after a query separator followed by that much opaque token.
|
|
6624
6734
|
["presigned_signature", /(?<=[?&](?:X-Amz-Signature|X-Goog-Signature|sig)=)[A-Za-z0-9%+/=_-]{16,}/gi],
|
|
6625
|
-
// A password inside a connection url. `postgres://user:hunter2@db.internal` carries the
|
|
6626
|
-
// credential in the authority section, where there is no `key=value` separator for the
|
|
6627
|
-
// generic pattern below to anchor on, so a DATABASE_URL echoed by a failing migration or a
|
|
6628
|
-
// psql error went through untouched. The anchors are what keep this narrow: a scheme's `://`,
|
|
6629
|
-
// a userinfo segment, the colon, and a following `@`. `http://host:8080/path` has no `@` and
|
|
6630
|
-
// is left alone; so is any url without credentials.
|
|
6735
|
+
// A password inside a connection url. `postgres://user:hunter2@db.internal` carries the credential in the authority section, where there is no `key=value` separator for the generic pattern below to anchor on, so a DATABASE_URL echoed by a failing migration or a psql error went through untouched. The anchors are what keep this narrow: a scheme's `://`, a userinfo segment, the colon, and a following `@`. `http://host:8080/path` has no `@` and is left alone; so is any url without credentials.
|
|
6631
6736
|
["url_credentials", /(?<=:\/\/[^\s:@/]{1,64}:)[^\s:@/]{1,256}(?=@)/g],
|
|
6632
|
-
//
|
|
6633
|
-
|
|
6634
|
-
// excludes whitespace, '&', ';', '#', quote characters, and '[' ']' ':' -- that exclusion is
|
|
6635
|
-
// what stops this from swallowing the rest of the line (a trailing comment or the next
|
|
6636
|
-
// key=value pair) or the remainder of a query string past the matched parameter, which is
|
|
6637
|
-
// exactly the kind of over-eager match this module's own design note above warns broad
|
|
6638
|
-
// heuristics produce. The '[' ']' ':' exclusion also matters because this pattern runs last: an
|
|
6639
|
-
// earlier pattern's own "OPENAI_API_KEY=[REDACTED:openai_project_key]" replacement text
|
|
6640
|
-
// contains "API_KEY=" too, and without excluding those characters this pattern would re-match
|
|
6641
|
-
// and double-redact its own placeholder. The length has a lower bound only (no upper bound):
|
|
6642
|
-
// capping it at 64 used to leave the tail of any longer secret unredacted in plain text, which
|
|
6643
|
-
// is worse than no redaction because it looks handled. A single negated-class quantifier like
|
|
6644
|
-
// this cannot backtrack catastrophically -- there is no nested or overlapping quantifier for
|
|
6645
|
-
// the engine to explore multiple ways of matching, so removing the upper bound does not
|
|
6646
|
-
// introduce a ReDoS risk.
|
|
6647
|
-
// Quotes are permitted around the separator, but never inside the value class. A quoted value
|
|
6648
|
-
// is the ordinary way secrets are written -- .env files, JSON, YAML, TOML all quote by default
|
|
6649
|
-
// -- and the lookbehind used to stop dead at the opening quote, so `API_KEY="..."` passed
|
|
6650
|
-
// through in full while the bare `API_KEY=...` was caught. The closing quote of the key name
|
|
6651
|
-
// blocked it from the other side too, which is what kept every JSON body unredacted. Keeping
|
|
6652
|
-
// quotes out of the value class is still what stops the match running past the closing quote.
|
|
6653
|
-
// The keyword may be a prefix of a longer key name rather than the whole of it, so the
|
|
6654
|
-
// trailing identifier class below is load-bearing: without it the lookbehind required the
|
|
6655
|
-
// keyword to sit immediately before the separator, and AWS_SECRET_ACCESS_KEY=,
|
|
6656
|
-
// SECRET_KEY=, and DB_PASSWORD_HASH= all passed through in full. That class matches
|
|
6657
|
-
// identifier characters only, so prose that merely mentions a keyword still never reaches
|
|
6658
|
-
// a separator and stays unredacted. api[_-]?key covers the apikey and api-key spellings too.
|
|
6659
|
-
// `& ; # , :` play two incompatible roles. They separate one field from the next (a query
|
|
6660
|
-
// string, a cookie header, an inline env list), and they are also perfectly ordinary password
|
|
6661
|
-
// characters. Rejecting them outright got the first role right and the second badly wrong: the
|
|
6662
|
-
// match stopped at the first one and left everything after it in plain text, so
|
|
6663
|
-
// `password=corr&horse&battery` redacted four characters and printed the rest, and
|
|
6664
|
-
// `DB_PASSWORD=Aa1:xyz` matched nothing at all because the run before the `:` was under the
|
|
6665
|
-
// four-character floor. A tail left sitting in the open is the outcome this module's header
|
|
6666
|
-
// calls worse than no redaction, because it reads as handled.
|
|
6737
|
+
// Azure storage account connection strings (`DefaultEndpointsProtocol=https;AccountName=...; AccountKey=<base64>==;EndpointSuffix=core.windows.net`) carry a full read/write key to the account in the AccountKey field, and none of the generic patterns above catch it: generic_secret_assignment's keyword list (password|passwd|secret|api[_-]?key| access[_-]?token|refresh[_-]?token|id[_-]?token) has nothing that matches "AccountKey", and an unanchored base64-shape pattern was deliberately rejected -- this module's header already warns that a bare high-entropy-blob heuristic false-fires on ordinary code, JSON and log output, and an 88-char base64 value is exactly the shape a hash, a compiled asset digest or a generated id can also take. Anchoring on the literal `AccountKey=` field name instead keeps the match specific to this one connection-string field. The value class is base64 proper (letters, digits, `+`, `/`, trailing `=` padding) and none of those characters include `;`, so the match terminates on its own at the `;` that starts the next `Name=` field -- unlike generic_secret_assignment's separator characters (`& ; # , :`), which double as ordinary credential characters and need a lookahead to tell the two roles apart, `;` is never valid base64 and needs no such lookahead here. The lookbehind keeps `AccountKey=` itself in the output, matching auth_bearer_token and presigned_signature above, so `;EndpointSuffix=core.windows.net` after it stays fully readable too. `SharedAccessKey` is the same credential one Azure service over: Service Bus, Event Hubs and Relay spell it that way (`Endpoint=sb://ns.servicebus.windows.net/;SharedAccessKeyName=Root; SharedAccessKey=<base64>`) and it carries the same authority over that namespace that AccountKey does over a storage account. `SharedAccessKeyName` is a plain identifier rather than a secret, and never matches: the separator in the lookbehind sits directly against the key name, so the `Name` in between stops it dead. The separator is spelled the way auth_bearer_token above spells its own, for the same reasons that comment records having learned the hard way. Optional quotes on either side of it, so the JSON and YAML forms a logged MCP result or api response actually arrives in are matched rather than stopped dead at the opening quote. A bounded gap rather than exactly one space, so a hand-aligned or reformatted `AccountKey = ...` in an appsettings file is not the single variant that defeats the whole pattern. Case-insensitive for the same reason presigned_signature is. The leading word boundary is what keeps the widened name from reaching into the middle of a longer identifier such as `myaccountkey=`.
|
|
6738
|
+
["azure_storage_key", /(?<=\b(?:AccountKey|SharedAccessKey)["']?[ \t]{0,8}[:=][ \t]{0,8}["']?)[A-Za-z0-9+/]{40,}=*/gi],
|
|
6739
|
+
// Generic key=value assignments in .env-file and connection-string/query-string shape. The lookbehind again redacts only the value, and the value's character class deliberately excludes whitespace, '&', ';', '#', quote characters, and '[' ']' ':' -- that exclusion is what stops this from swallowing the rest of the line (a trailing comment or the next key=value pair) or the remainder of a query string past the matched parameter, which is exactly the kind of over-eager match this module's own design note above warns broad heuristics produce. The '[' ']' ':' exclusion also matters because this pattern runs last: an earlier pattern's own "OPENAI_API_KEY=[REDACTED:openai_project_key]" replacement text contains "API_KEY=" too, and without excluding those characters this pattern would re-match and double-redact its own placeholder. The length has a lower bound only (no upper bound): capping it at 64 used to leave the tail of any longer secret unredacted in plain text, which is worse than no redaction because it looks handled. A single negated-class quantifier like this cannot backtrack catastrophically -- there is no nested or overlapping quantifier for the engine to explore multiple ways of matching, so removing the upper bound does not introduce a ReDoS risk. Quotes are permitted around the separator, but never inside the value class. A quoted value is the ordinary way secrets are written -- .env files, JSON, YAML, TOML all quote by default -- and the lookbehind used to stop dead at the opening quote, so `API_KEY="..."` passed through in full while the bare `API_KEY=...` was caught. The closing quote of the key name blocked it from the other side too, which is what kept every JSON body unredacted. Keeping quotes out of the value class is still what stops the match running past the closing quote. The keyword may be a prefix of a longer key name rather than the whole of it, so the trailing identifier class below is load-bearing: without it the lookbehind required the keyword to sit immediately before the separator, and AWS_SECRET_ACCESS_KEY=, SECRET_KEY=, and DB_PASSWORD_HASH= all passed through in full. That class matches identifier characters only, so prose that merely mentions a keyword still never reaches a separator and stays unredacted. api[_-]?key covers the apikey and api-key spellings too. `& ; # , :` play two incompatible roles. They separate one field from the next (a query string, a cookie header, an inline env list), and they are also perfectly ordinary credential characters. Rejecting them outright got the first role right and the second badly wrong: the match stopped at the first one and left everything after it in plain text, so `password=corr&horse&battery` redacted four characters and printed the rest, and `DB_PASSWORD=Aa1:xyz` matched nothing at all because the run before the `:` was under the four-character floor. A tail left sitting in the open is the outcome this module's header calls worse than no redaction, because it reads as handled.
|
|
6667
6740
|
//
|
|
6668
|
-
// So the separator role is decided by what follows rather than assumed: one of these characters
|
|
6669
|
-
// ends the value only when the next thing along is another `name=` / `name:` pair, which is what
|
|
6670
|
-
// an actual field separator is always followed by. `,OTHER=public` and `; other=1` still end it;
|
|
6671
|
-
// the `&` in the middle of a passphrase does not. Whitespace, quotes and brackets are unchanged
|
|
6672
|
-
// -- they end a value unconditionally, which is also what keeps this pattern from re-matching
|
|
6673
|
-
// the `[REDACTED:...]` placeholder it just wrote.
|
|
6741
|
+
// So the separator role is decided by what follows rather than assumed: one of these characters ends the value only when the next thing along is another `name=` / `name:` pair, which is what an actual field separator is always followed by. `,OTHER=public` and `; other=1` still end it; the `&` in the middle of a passphrase does not. Whitespace, quotes and brackets are unchanged -- they end a value unconditionally, which is also what keeps this pattern from re-matching the `[REDACTED:...]` placeholder it just wrote.
|
|
6674
6742
|
["generic_secret_assignment", /(?<=(?:password|passwd|secret|api[_-]?key|access[_-]?token|refresh[_-]?token|id[_-]?token)[a-z0-9_-]{0,64}["']?[ \t]{0,8}[:=][ \t]{0,8}["']?)(?:\\[^\n]|[^\s\\&;#,:'"[\]{}]|[&;#,:](?![ \t]*[A-Za-z_][A-Za-z0-9_.-]*[ \t]*[:=])){4,}/gi]
|
|
6675
6743
|
];
|
|
6744
|
+
function countRedactionPlaceholders(text) {
|
|
6745
|
+
return text.match(/\[REDACTED:[a-z0-9_]+\]/g)?.length ?? 0;
|
|
6746
|
+
}
|
|
6676
6747
|
function redactSecrets(text) {
|
|
6677
6748
|
let count = 0;
|
|
6678
6749
|
let out = text;
|
|
@@ -6896,6 +6967,7 @@ export {
|
|
|
6896
6967
|
_useRichStats,
|
|
6897
6968
|
renderShortStats,
|
|
6898
6969
|
renderStats2 as renderStats,
|
|
6970
|
+
countRedactionPlaceholders,
|
|
6899
6971
|
redactSecrets,
|
|
6900
6972
|
FILTERS,
|
|
6901
6973
|
stripAnsiCodes
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
runSkeleton,
|
|
26
26
|
runSymbol,
|
|
27
27
|
withPinnedReads
|
|
28
|
-
} from "./token-goat-chunk-
|
|
28
|
+
} from "./token-goat-chunk-TX4JFJTD.mjs";
|
|
29
29
|
import {
|
|
30
30
|
buildProjectMap,
|
|
31
31
|
embeddingsDepsAvailable,
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
getProjectIndexCounts,
|
|
35
35
|
isWorkerRunning,
|
|
36
36
|
mapLookupBytesSaved
|
|
37
|
-
} from "./token-goat-chunk-
|
|
37
|
+
} from "./token-goat-chunk-PWVXXPCC.mjs";
|
|
38
38
|
import {
|
|
39
39
|
VERSION,
|
|
40
40
|
dataDir,
|
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
normalizePath,
|
|
46
46
|
recordStat,
|
|
47
47
|
resolveProjectRoot
|
|
48
|
-
} from "./token-goat-chunk-
|
|
48
|
+
} from "./token-goat-chunk-6ODZ6PZK.mjs";
|
|
49
49
|
import "./token-goat-chunk-AO2QD2AG.mjs";
|
|
50
50
|
import "./token-goat-chunk-AEX54RUZ.mjs";
|
|
51
51
|
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
buildEvent,
|
|
5
5
|
relay,
|
|
6
6
|
relayInProcess
|
|
7
|
-
} from "./token-goat-chunk-
|
|
7
|
+
} from "./token-goat-chunk-NKNCHJ4H.mjs";
|
|
8
8
|
import {
|
|
9
9
|
MAX_STDIN_BYTES,
|
|
10
10
|
readStdinJson
|
|
11
|
-
} from "./token-goat-chunk-
|
|
12
|
-
import "./token-goat-chunk-
|
|
13
|
-
import "./token-goat-chunk-
|
|
14
|
-
import "./token-goat-chunk-
|
|
11
|
+
} from "./token-goat-chunk-4HIMCBYK.mjs";
|
|
12
|
+
import "./token-goat-chunk-PWVXXPCC.mjs";
|
|
13
|
+
import "./token-goat-chunk-EFF2XCLB.mjs";
|
|
14
|
+
import "./token-goat-chunk-6ODZ6PZK.mjs";
|
|
15
15
|
import "./token-goat-chunk-AO2QD2AG.mjs";
|
|
16
16
|
import "./token-goat-chunk-AEX54RUZ.mjs";
|
|
17
17
|
export {
|