@wrongstack/plugins 0.307.1 → 0.308.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/dist/accessibility-auditor/index.d.ts +5 -0
- package/dist/accessibility-auditor.js +67 -11
- package/dist/audit/index.d.ts +6 -6
- package/dist/audit.js +7 -7
- package/dist/code-metrics/index.d.ts +9 -0
- package/dist/code-metrics.js +42 -11
- package/dist/dep-guard.js +4 -2
- package/dist/dependency-vulnerability-gate/index.d.ts +4 -0
- package/dist/dependency-vulnerability-gate.js +435 -62
- package/dist/factories/index.d.ts +1 -1
- package/dist/factories.js +1 -1
- package/dist/file-watcher.js +25 -10
- package/dist/format-on-save.js +10 -4
- package/dist/import-organizer/index.d.ts +4 -0
- package/dist/import-organizer.js +26 -1
- package/dist/index.js +719 -234
- package/dist/license-audit-gate/index.d.ts +1 -0
- package/dist/license-audit-gate.js +445 -83
- package/dist/lint-gate/index.d.ts +13 -0
- package/dist/lint-gate.js +13 -10
- package/dist/llm-cache.js +6 -0
- package/dist/manifest.js +1 -1
- package/dist/path-guard/glob.d.ts +23 -0
- package/dist/path-guard/index.d.ts +2 -0
- package/dist/path-guard.js +214 -32
- package/dist/plugin-audit-catalog.js +7 -7
- package/dist/prompt-firewall/index.d.ts +49 -40
- package/dist/prompt-firewall.js +302 -29
- package/dist/runtime/h1-state.d.ts +62 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/redos-guard.d.ts +69 -0
- package/dist/runtime/sandbox.d.ts +59 -0
- package/dist/runtime.js +210 -19
- package/dist/secret-scanner.js +16 -2
- package/dist/spec-linker.js +1 -1
- package/dist/type-gate.js +33 -3
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -345,6 +345,83 @@ function releaseHandle(off) {
|
|
|
345
345
|
return null;
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
// src/runtime/redos-guard.ts
|
|
349
|
+
import { Worker } from "node:worker_threads";
|
|
350
|
+
function withReDoSGuard(re, input, budgetMs = 50, options = {}) {
|
|
351
|
+
const opts = { budgetMs, ...options };
|
|
352
|
+
const start = Date.now();
|
|
353
|
+
const workerSource = buildWorkerSource(re.source, input, re.flags);
|
|
354
|
+
const worker = new Worker(workerSource, {
|
|
355
|
+
eval: true,
|
|
356
|
+
name: `redos-guard:${re.source.slice(0, 32)}`
|
|
357
|
+
});
|
|
358
|
+
return new Promise((resolve29) => {
|
|
359
|
+
let settled = false;
|
|
360
|
+
const onMessage = (msg) => {
|
|
361
|
+
if (settled) return;
|
|
362
|
+
settled = true;
|
|
363
|
+
clearTimeout(timer);
|
|
364
|
+
worker.terminate().catch(() => {
|
|
365
|
+
});
|
|
366
|
+
if (!msg.ok) {
|
|
367
|
+
resolve29({ timedOut: true, match: null });
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
resolve29({ timedOut: false, match: msg.match });
|
|
371
|
+
};
|
|
372
|
+
const onError = () => {
|
|
373
|
+
if (settled) return;
|
|
374
|
+
settled = true;
|
|
375
|
+
clearTimeout(timer);
|
|
376
|
+
worker.terminate().catch(() => {
|
|
377
|
+
});
|
|
378
|
+
resolve29({ timedOut: true, match: null });
|
|
379
|
+
};
|
|
380
|
+
const timer = setTimeout(() => {
|
|
381
|
+
if (settled) return;
|
|
382
|
+
settled = true;
|
|
383
|
+
const elapsedMs = Date.now() - start;
|
|
384
|
+
worker.terminate().catch(() => {
|
|
385
|
+
});
|
|
386
|
+
try {
|
|
387
|
+
opts.onTimeout?.({
|
|
388
|
+
regex: re,
|
|
389
|
+
input,
|
|
390
|
+
budgetMs: opts.budgetMs,
|
|
391
|
+
elapsedMs
|
|
392
|
+
});
|
|
393
|
+
} catch {
|
|
394
|
+
}
|
|
395
|
+
resolve29({ timedOut: true, match: null });
|
|
396
|
+
}, opts.budgetMs);
|
|
397
|
+
timer.unref?.();
|
|
398
|
+
worker.on("message", onMessage);
|
|
399
|
+
worker.on("error", onError);
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
function buildWorkerSource(source, input, flags) {
|
|
403
|
+
const S = JSON.stringify(source);
|
|
404
|
+
const I = JSON.stringify(input);
|
|
405
|
+
const F = JSON.stringify(flags);
|
|
406
|
+
return `
|
|
407
|
+
const { parentPort } = require('node:worker_threads');
|
|
408
|
+
const source = ${S};
|
|
409
|
+
const input = ${I};
|
|
410
|
+
const flags = ${F};
|
|
411
|
+
try {
|
|
412
|
+
const re = new RegExp(source, flags);
|
|
413
|
+
const match = re.exec(input);
|
|
414
|
+
// parentPort.postMessage, NOT bare postMessage: with eval:true
|
|
415
|
+
// workers this Node version does not expose the bare postMessage
|
|
416
|
+
// global \u2014 the worker throws ReferenceError at startup and the
|
|
417
|
+
// host misreads it as a timeout (positive-path regression).
|
|
418
|
+
parentPort.postMessage({ ok: true, match });
|
|
419
|
+
} catch (err) {
|
|
420
|
+
parentPort.postMessage({ ok: false, error: err && err.message ? err.message : String(err) });
|
|
421
|
+
}
|
|
422
|
+
`;
|
|
423
|
+
}
|
|
424
|
+
|
|
348
425
|
// src/runtime/index.ts
|
|
349
426
|
var META_CHARS = /["'`;&|<>\r\n]/;
|
|
350
427
|
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
@@ -446,6 +523,7 @@ function runRunnerCommand(argv, options) {
|
|
|
446
523
|
}
|
|
447
524
|
let timedOut = false;
|
|
448
525
|
let spawnErrored = false;
|
|
526
|
+
const start = Date.now();
|
|
449
527
|
const stdoutChunks = [];
|
|
450
528
|
const stderrChunks = [];
|
|
451
529
|
let stdoutBytes = 0;
|
|
@@ -475,6 +553,15 @@ function runRunnerCommand(argv, options) {
|
|
|
475
553
|
timeout: options.timeoutMs,
|
|
476
554
|
signal: options.signal,
|
|
477
555
|
maxBuffer: MAX_BUFFER_BYTES,
|
|
556
|
+
// execFile defaults `encoding` to 'utf8', which makes the
|
|
557
|
+
// stdout/stderr `data` events emit *strings*. The chunk arrays
|
|
558
|
+
// below are typed Buffer[] and every consumer runs them through
|
|
559
|
+
// Buffer.concat(...).toString('utf8'), which throws
|
|
560
|
+
// ERR_INVALID_ARG_TYPE on any non-empty string output. Pin the
|
|
561
|
+
// streams to buffers so the declared contract holds (regression:
|
|
562
|
+
// runRunnerCommand crashed on any child that actually wrote
|
|
563
|
+
// output; only the maxBuffer fixture exercised this path).
|
|
564
|
+
encoding: "buffer",
|
|
478
565
|
windowsHide: true,
|
|
479
566
|
shell: false,
|
|
480
567
|
...invocation.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
|
|
@@ -503,11 +590,31 @@ function runRunnerCommand(argv, options) {
|
|
|
503
590
|
}
|
|
504
591
|
if (err) {
|
|
505
592
|
const anyErr = err;
|
|
593
|
+
if ((anyErr.killed === true || anyErr.signal === "SIGTERM") && // maxBuffer overflow must NOT be misreported as a timeout
|
|
594
|
+
// — it's a real failure (the child wrote too much) and
|
|
595
|
+
// downstream callers (type-gate/index.ts:227) return
|
|
596
|
+
// null on timedOut=true, which would silently swallow
|
|
597
|
+
// maxBuffer overflow into a confusing empty-output
|
|
598
|
+
// result. Skip the timeout resolve when the err shape
|
|
599
|
+
// names maxBuffer explicitly.
|
|
600
|
+
!/maxBuffer length exceeded/i.test(anyErr.message ?? "") && // And only count it as a timeout if the wall clock has
|
|
601
|
+
// actually elapsed past the budget. External SIGTERMs and
|
|
602
|
+
// races against the exit handler don't satisfy this.
|
|
603
|
+
Date.now() - start >= options.timeoutMs) {
|
|
604
|
+
resolvePromise({
|
|
605
|
+
code: null,
|
|
606
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
607
|
+
stderr: Buffer.concat(stderrChunks).toString("utf-8"),
|
|
608
|
+
timedOut: true,
|
|
609
|
+
spawnError: false
|
|
610
|
+
});
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
506
613
|
const code = typeof anyErr.code === "number" ? anyErr.code : 1;
|
|
507
614
|
resolvePromise({
|
|
508
615
|
code,
|
|
509
|
-
stdout: Buffer.concat(stdoutChunks).toString("
|
|
510
|
-
stderr: Buffer.concat(stderrChunks).toString("
|
|
616
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
617
|
+
stderr: Buffer.concat(stderrChunks).toString("utf-8"),
|
|
511
618
|
timedOut: false,
|
|
512
619
|
spawnError: false
|
|
513
620
|
});
|
|
@@ -523,7 +630,7 @@ function runRunnerCommand(argv, options) {
|
|
|
523
630
|
}
|
|
524
631
|
);
|
|
525
632
|
child.on("exit", (_code, signal) => {
|
|
526
|
-
if (signal !== null) {
|
|
633
|
+
if (signal !== null && Date.now() - start >= options.timeoutMs) {
|
|
527
634
|
timedOut = true;
|
|
528
635
|
}
|
|
529
636
|
});
|
|
@@ -626,15 +733,40 @@ var INPUT_BUTTON = /<input\b[^>]*\btype\s*=\s*["'](submit|button|reset)["'][^>]*
|
|
|
626
733
|
var ATTR_ID = /\bid\s*=\s*["']([^"']+)["']/gi;
|
|
627
734
|
var ATTR_ALT = /\balt\s*=/i;
|
|
628
735
|
var ATTR_ARIA_LABEL = /\b(?:aria-label|aria-labelledby)\s*=/i;
|
|
736
|
+
var ATTR_ARIA_DESCRIBEDBY = /\baria-describedby\s*=/i;
|
|
629
737
|
var ATTR_TITLE = /\btitle\s*=/i;
|
|
630
738
|
var ATTR_PLACEHOLDER = /\bplaceholder\s*=/i;
|
|
631
739
|
var ATTR_VALUE = /\bvalue\s*=/i;
|
|
740
|
+
var ATTR_ROLE_DECORATIVE = /\brole\s*=\s*["'](?:presentation|none)["']/i;
|
|
741
|
+
var SINGLE_FILE_LABEL_NOTE = "Single-file heuristic: a label declared in a sibling component file is not visible to this scan.";
|
|
742
|
+
function escapeRegExp(value) {
|
|
743
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
744
|
+
}
|
|
632
745
|
function hasMeaningfulAlt(tag) {
|
|
633
746
|
const m = ATTR_ALT.exec(tag);
|
|
634
747
|
ATTR_ALT.lastIndex = 0;
|
|
635
748
|
if (!m) return false;
|
|
636
749
|
const valMatch = tag.match(/\balt\s*=\s*["']?([^"'\s>]*)["']?/i);
|
|
637
|
-
|
|
750
|
+
const alt = valMatch ? valMatch[1].trim() : "";
|
|
751
|
+
if (alt.length > 0) return true;
|
|
752
|
+
return ATTR_ROLE_DECORATIVE.test(tag);
|
|
753
|
+
}
|
|
754
|
+
function hasFieldsetLegendLabel(tag, content) {
|
|
755
|
+
const labelledBy = tag.match(/\baria-labelledby\s*=\s*["']([^"']+)["']/i);
|
|
756
|
+
if (labelledBy?.[1]) {
|
|
757
|
+
for (const id of labelledBy[1].split(/\s+/).filter(Boolean)) {
|
|
758
|
+
const idRe = new RegExp(`\\bid\\s*=\\s*["']${escapeRegExp(id)}["']`, "i");
|
|
759
|
+
if (idRe.test(content)) return true;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
const typeMatch = tag.match(/\btype\s*=\s*["']?([^"'\s>]*)["']?/i);
|
|
763
|
+
const type = typeMatch ? typeMatch[1].toLowerCase() : "text";
|
|
764
|
+
if (type === "checkbox" || type === "radio") {
|
|
765
|
+
return /<fieldset\b[\s\S]*?<legend\b[\s\S]*?<\/legend>[\s\S]*?<input\b[\s\S]*?<\/fieldset>/i.test(
|
|
766
|
+
content
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
return false;
|
|
638
770
|
}
|
|
639
771
|
async function auditFile(filePath, projectRoot) {
|
|
640
772
|
let content;
|
|
@@ -646,13 +778,14 @@ async function auditFile(filePath, projectRoot) {
|
|
|
646
778
|
const findings = [];
|
|
647
779
|
const lines = content.split(/\r?\n/);
|
|
648
780
|
const idsByValue = /* @__PURE__ */ new Map();
|
|
649
|
-
function add(line, rule, severity, message) {
|
|
781
|
+
function add(line, rule, severity, message, note) {
|
|
650
782
|
findings.push({
|
|
651
783
|
file: relative3(projectRoot, filePath),
|
|
652
784
|
line,
|
|
653
785
|
rule,
|
|
654
786
|
severity,
|
|
655
|
-
message
|
|
787
|
+
message,
|
|
788
|
+
...note ? { note } : {}
|
|
656
789
|
});
|
|
657
790
|
}
|
|
658
791
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -680,17 +813,36 @@ async function auditFile(filePath, projectRoot) {
|
|
|
680
813
|
continue;
|
|
681
814
|
}
|
|
682
815
|
const hasAriaLabel = ATTR_ARIA_LABEL.test(tag);
|
|
816
|
+
const hasDescribedBy = ATTR_ARIA_DESCRIBEDBY.test(tag);
|
|
683
817
|
const hasTitle = ATTR_TITLE.test(tag);
|
|
684
818
|
const idMatchLocal = tag.match(/\bid\s*=\s*["']([^"']+)["']/i);
|
|
685
819
|
const id = idMatchLocal ? idMatchLocal[1] : null;
|
|
686
820
|
let hasLabelFor = false;
|
|
687
821
|
if (id) {
|
|
688
|
-
const labelForRe = new RegExp(
|
|
822
|
+
const labelForRe = new RegExp(
|
|
823
|
+
`<label\\b[^>]*\\bfor\\s*=\\s*["']${escapeRegExp(id)}["']`,
|
|
824
|
+
"i"
|
|
825
|
+
);
|
|
689
826
|
hasLabelFor = labelForRe.test(content);
|
|
690
827
|
}
|
|
691
828
|
const wrappedInLabel = /<label\b[\s\S]*?<input\b[\s\S]*?<\/label>/i.test(content);
|
|
692
|
-
|
|
693
|
-
|
|
829
|
+
const hasLegend = hasFieldsetLegendLabel(tag, content);
|
|
830
|
+
const hasPrimaryLabel = hasAriaLabel || hasTitle || hasLabelFor || wrappedInLabel || hasLegend;
|
|
831
|
+
if (!hasPrimaryLabel && hasDescribedBy) {
|
|
832
|
+
add(
|
|
833
|
+
lineNo,
|
|
834
|
+
"low-contrast-placeholder",
|
|
835
|
+
"warning",
|
|
836
|
+
`<input type="${type}"> uses aria-describedby as a description (supplementary, not a primary label)`
|
|
837
|
+
);
|
|
838
|
+
} else if (!hasPrimaryLabel) {
|
|
839
|
+
add(
|
|
840
|
+
lineNo,
|
|
841
|
+
"missing-input-label",
|
|
842
|
+
"error",
|
|
843
|
+
`<input type="${type}"> is missing an associated label`,
|
|
844
|
+
SINGLE_FILE_LABEL_NOTE
|
|
845
|
+
);
|
|
694
846
|
}
|
|
695
847
|
if (ATTR_PLACEHOLDER.test(tag)) {
|
|
696
848
|
add(lineNo, "low-contrast-placeholder", "warning", "<input> uses placeholder text (often low contrast and disappears on input)");
|
|
@@ -750,15 +902,24 @@ async function auditPath(rawPath, cfg) {
|
|
|
750
902
|
truncated
|
|
751
903
|
};
|
|
752
904
|
}
|
|
905
|
+
function truncationWarning(result) {
|
|
906
|
+
if (!result.truncated) return "";
|
|
907
|
+
const unexamined = Math.max(0, result.fileCount - (result.scannedFiles ?? result.fileCount));
|
|
908
|
+
return `partial scan \u2014 ${unexamined} files not examined`;
|
|
909
|
+
}
|
|
753
910
|
function formatSummary(result) {
|
|
911
|
+
const trunc = truncationWarning(result);
|
|
754
912
|
if (result.findings.length === 0) {
|
|
755
|
-
|
|
913
|
+
const clean = `
|
|
756
914
|
\u2705 accessibility-auditor: no issues found in ${result.path} (${result.fileCount} file${result.fileCount === 1 ? "" : "s"}).`;
|
|
915
|
+
return trunc ? `${clean}
|
|
916
|
+
\u26A0\uFE0F ${trunc}` : clean;
|
|
757
917
|
}
|
|
758
918
|
const lines = result.findings.map((f) => ` - ${f.file}:${f.line} \u2014 ${f.message} (${f.rule})`);
|
|
759
919
|
return `
|
|
760
920
|
\u26A0\uFE0F accessibility-auditor: ${result.findings.length} issue(s) in ${result.path} (${result.fileCount} file${result.fileCount === 1 ? "" : "s"}):
|
|
761
|
-
` + lines.join("\n") + "\nConsider adding missing labels/alt text or resolving duplicate ids."
|
|
921
|
+
` + lines.join("\n") + "\nConsider adding missing labels/alt text or resolving duplicate ids." + (trunc ? `
|
|
922
|
+
\u26A0\uFE0F ${trunc}` : "");
|
|
762
923
|
}
|
|
763
924
|
var plugin = {
|
|
764
925
|
name: "accessibility-auditor",
|
|
@@ -829,9 +990,9 @@ var plugin = {
|
|
|
829
990
|
findingCount: result.findings.length,
|
|
830
991
|
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
831
992
|
};
|
|
832
|
-
if (result.findings.length === 0) return;
|
|
993
|
+
if (result.findings.length === 0 && !result.truncated) return;
|
|
833
994
|
const summary = formatSummary(result);
|
|
834
|
-
if (cfg.severity === "block") {
|
|
995
|
+
if (cfg.severity === "block" && result.findings.length > 0) {
|
|
835
996
|
return { decision: "block", reason: summary };
|
|
836
997
|
}
|
|
837
998
|
return { additionalContext: summary };
|
|
@@ -872,6 +1033,7 @@ var plugin = {
|
|
|
872
1033
|
findingCount: result.findings.length,
|
|
873
1034
|
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
874
1035
|
};
|
|
1036
|
+
const warning = truncationWarning(result);
|
|
875
1037
|
return {
|
|
876
1038
|
ok: true,
|
|
877
1039
|
path: result.path,
|
|
@@ -881,7 +1043,8 @@ var plugin = {
|
|
|
881
1043
|
// that reports few findings must not read as a clean result.
|
|
882
1044
|
truncated: result.truncated,
|
|
883
1045
|
findingCount: result.findings.length,
|
|
884
|
-
findings: result.findings
|
|
1046
|
+
findings: result.findings,
|
|
1047
|
+
...warning ? { additionalContext: `\u26A0\uFE0F ${warning}`, warning } : {}
|
|
885
1048
|
};
|
|
886
1049
|
}
|
|
887
1050
|
});
|
|
@@ -3299,9 +3462,9 @@ var plugin9 = {
|
|
|
3299
3462
|
const ti = input.toolInput ?? {};
|
|
3300
3463
|
const raw = ti["path"] ?? ti["file_path"] ?? ti["filePath"];
|
|
3301
3464
|
if (typeof raw !== "string" || raw.length === 0) return;
|
|
3302
|
-
const
|
|
3303
|
-
if (!
|
|
3304
|
-
const captured = await captureFileForHook(
|
|
3465
|
+
const safePath2 = resolveProjectPath3(raw);
|
|
3466
|
+
if (!safePath2) return;
|
|
3467
|
+
const captured = await captureFileForHook(safePath2, cfg.maxFileBytes, runtime.signal);
|
|
3305
3468
|
if (captured === "too-large") {
|
|
3306
3469
|
state9.skippedLarge += 1;
|
|
3307
3470
|
return;
|
|
@@ -3319,7 +3482,7 @@ var plugin9 = {
|
|
|
3319
3482
|
state9.captures += 1;
|
|
3320
3483
|
api.metrics.counter("captures");
|
|
3321
3484
|
api.emitCustom?.("checkpoint:captured", {
|
|
3322
|
-
path:
|
|
3485
|
+
path: safePath2,
|
|
3323
3486
|
bytes: captured.bytes,
|
|
3324
3487
|
hadContent: captured.content !== null,
|
|
3325
3488
|
// 32-bit unsigned hash of the captured bytes. Collisions
|
|
@@ -3363,12 +3526,12 @@ var plugin9 = {
|
|
|
3363
3526
|
const rejectedOutsideProject = [];
|
|
3364
3527
|
let skipped = 0;
|
|
3365
3528
|
for (const p of paths) {
|
|
3366
|
-
const
|
|
3367
|
-
if (!
|
|
3529
|
+
const safePath2 = resolveProjectPath3(p);
|
|
3530
|
+
if (!safePath2) {
|
|
3368
3531
|
rejectedOutsideProject.push(p);
|
|
3369
3532
|
continue;
|
|
3370
3533
|
}
|
|
3371
|
-
const captured = await captureFile(
|
|
3534
|
+
const captured = await captureFile(safePath2, cfg.maxFileBytes);
|
|
3372
3535
|
if (captured === "too-large") {
|
|
3373
3536
|
skipped += 1;
|
|
3374
3537
|
state9.skippedLarge += 1;
|
|
@@ -3601,36 +3764,65 @@ function countFunctions(content) {
|
|
|
3601
3764
|
for (const _match of content.matchAll(arrowRe)) count++;
|
|
3602
3765
|
return count;
|
|
3603
3766
|
}
|
|
3767
|
+
var COMPLEXITY_FORMULA = "control(if|else if|for|while|switch|catch) + (&& || ?? ||= &&= ??=) + ternary ?; optional chaining ?. is not counted";
|
|
3604
3768
|
function countComplexity(content) {
|
|
3605
3769
|
const controlRe = /\b(if|else\s+if|for|while|switch|catch)\b/g;
|
|
3606
|
-
const operatorRe = /[?&|]/g;
|
|
3607
3770
|
let complexity = 0;
|
|
3608
3771
|
controlRe.lastIndex = 0;
|
|
3609
3772
|
for (const _match of content.matchAll(controlRe)) complexity++;
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
const
|
|
3613
|
-
|
|
3773
|
+
for (let i = 0; i < content.length; i++) {
|
|
3774
|
+
const c = content[i];
|
|
3775
|
+
const n1 = content[i + 1];
|
|
3776
|
+
const n2 = content[i + 2];
|
|
3777
|
+
if (c === "?" && n1 === "?" && n2 === "=") {
|
|
3614
3778
|
complexity++;
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3779
|
+
i += 2;
|
|
3780
|
+
continue;
|
|
3781
|
+
}
|
|
3782
|
+
if (c === "|" && n1 === "|" && n2 === "=" || c === "&" && n1 === "&" && n2 === "=") {
|
|
3783
|
+
complexity++;
|
|
3784
|
+
i += 2;
|
|
3785
|
+
continue;
|
|
3618
3786
|
}
|
|
3787
|
+
if (c === "?" && n1 === "?" || c === "|" && n1 === "|" || c === "&" && n1 === "&") {
|
|
3788
|
+
complexity++;
|
|
3789
|
+
i += 1;
|
|
3790
|
+
continue;
|
|
3791
|
+
}
|
|
3792
|
+
if (c === "?" && n1 === ".") {
|
|
3793
|
+
i += 1;
|
|
3794
|
+
continue;
|
|
3795
|
+
}
|
|
3796
|
+
if (c === "?") complexity++;
|
|
3619
3797
|
}
|
|
3620
3798
|
return complexity;
|
|
3621
3799
|
}
|
|
3622
3800
|
function analyzeFile(filePath, content) {
|
|
3801
|
+
if (content.length === 0) {
|
|
3802
|
+
return {
|
|
3803
|
+
file: relativePath(filePath),
|
|
3804
|
+
lines: 0,
|
|
3805
|
+
codeLines: 0,
|
|
3806
|
+
commentLines: 0,
|
|
3807
|
+
blankLines: 0,
|
|
3808
|
+
functionCount: 0,
|
|
3809
|
+
complexity: 0
|
|
3810
|
+
};
|
|
3811
|
+
}
|
|
3623
3812
|
const lines = content.split(/\r?\n/);
|
|
3624
3813
|
let codeLines = 0;
|
|
3625
3814
|
let commentLines = 0;
|
|
3626
3815
|
let blankLines = 0;
|
|
3627
3816
|
let inBlockComment = false;
|
|
3628
|
-
for (
|
|
3629
|
-
const line =
|
|
3817
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3818
|
+
const line = lines[i].trim();
|
|
3630
3819
|
if (line.length === 0) {
|
|
3631
3820
|
blankLines++;
|
|
3632
3821
|
continue;
|
|
3633
3822
|
}
|
|
3823
|
+
if (i === 0 && line.startsWith("#!")) {
|
|
3824
|
+
continue;
|
|
3825
|
+
}
|
|
3634
3826
|
if (inBlockComment) {
|
|
3635
3827
|
commentLines++;
|
|
3636
3828
|
if (line.includes("*/")) inBlockComment = false;
|
|
@@ -3791,7 +3983,8 @@ var plugin10 = {
|
|
|
3791
3983
|
files: state10.fileCount,
|
|
3792
3984
|
hookInvocations: state10.hookInvocationCount,
|
|
3793
3985
|
errors: state10.errorCount
|
|
3794
|
-
}
|
|
3986
|
+
},
|
|
3987
|
+
complexityFormula: COMPLEXITY_FORMULA
|
|
3795
3988
|
};
|
|
3796
3989
|
}
|
|
3797
3990
|
});
|
|
@@ -5860,10 +6053,12 @@ function parseInstallCommands(command) {
|
|
|
5860
6053
|
if (!cleaned) continue;
|
|
5861
6054
|
let name = cleaned;
|
|
5862
6055
|
let version = null;
|
|
5863
|
-
const pipMatch = /^([A-Za-z0-9_.-]+)\s*(
|
|
6056
|
+
const pipMatch = /^([A-Za-z0-9_.-]+(?:\[[^\]]+\])?)\s*(==|>=|<=|~=|!=|>|<)\s*(.+)$/.exec(cleaned);
|
|
5864
6057
|
if (pipMatch?.[1]) {
|
|
5865
6058
|
name = pipMatch[1];
|
|
5866
|
-
|
|
6059
|
+
const op = pipMatch[2] ?? "";
|
|
6060
|
+
const ver = (pipMatch[3] ?? "").trim();
|
|
6061
|
+
version = op === "==" || op === "" ? ver || null : `${op}${ver}`;
|
|
5867
6062
|
} else {
|
|
5868
6063
|
const at = cleaned.lastIndexOf("@");
|
|
5869
6064
|
if (at > 0) {
|
|
@@ -6288,13 +6483,12 @@ function exceedsThreshold(report, threshold) {
|
|
|
6288
6483
|
if (thresholdRank == null || maxRank == null) return false;
|
|
6289
6484
|
return maxRank >= thresholdRank;
|
|
6290
6485
|
}
|
|
6291
|
-
var INSTALL_RE2 = /\b(?:npm\s+(?:install|i)|pnpm\s+add|yarn\s+add)\b/i;
|
|
6292
6486
|
function isInstallCommand(input) {
|
|
6293
6487
|
if (input.toolName === "install") return true;
|
|
6294
6488
|
if (input.toolName !== "bash" && input.toolName !== "exec") return false;
|
|
6295
6489
|
const ti = input.toolInput ?? {};
|
|
6296
6490
|
const command = typeof ti["command"] === "string" ? ti["command"] : "";
|
|
6297
|
-
return
|
|
6491
|
+
return parseInstallCommands(command).length > 0;
|
|
6298
6492
|
}
|
|
6299
6493
|
var LOCKFILE_MANAGERS = [
|
|
6300
6494
|
{ file: "pnpm-lock.yaml", manager: "pnpm" },
|
|
@@ -8067,18 +8261,8 @@ var feature_flag_tracker_default = plugin23;
|
|
|
8067
8261
|
|
|
8068
8262
|
// src/file-watcher/index.ts
|
|
8069
8263
|
import { watch as fsWatch } from "node:fs";
|
|
8070
|
-
import {
|
|
8264
|
+
import { join as join3 } from "node:path";
|
|
8071
8265
|
var API_VERSION16 = "^0.1.10";
|
|
8072
|
-
function withinProject3(p) {
|
|
8073
|
-
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
8074
|
-
const root = process.cwd();
|
|
8075
|
-
const resolved = isAbsolute12(p) ? resolve13(p) : resolve13(root, p);
|
|
8076
|
-
const rel = relative13(root, resolved);
|
|
8077
|
-
if (rel === "" || rel === ".") return true;
|
|
8078
|
-
if (rel.startsWith("..")) return false;
|
|
8079
|
-
if (isAbsolute12(rel)) return false;
|
|
8080
|
-
return true;
|
|
8081
|
-
}
|
|
8082
8266
|
var watch_idCounter = 0;
|
|
8083
8267
|
function nextId() {
|
|
8084
8268
|
return `watch_${++watch_idCounter}_${Date.now().toString(36)}`;
|
|
@@ -8159,7 +8343,7 @@ var plugin24 = {
|
|
|
8159
8343
|
}
|
|
8160
8344
|
const autoIndex = api.config.extensions?.["file-watcher"]?.["autoIndex"] ?? false;
|
|
8161
8345
|
const indexProjectRoot = api.config.extensions?.["file-watcher"]?.["indexProjectRoot"] ?? "";
|
|
8162
|
-
const safeIndexRoot = indexProjectRoot !== "" &&
|
|
8346
|
+
const safeIndexRoot = indexProjectRoot !== "" && withinProject(indexProjectRoot) ? indexProjectRoot : "";
|
|
8163
8347
|
if (indexProjectRoot !== "" && safeIndexRoot === "") {
|
|
8164
8348
|
api.log.warn(
|
|
8165
8349
|
"file-watcher: indexProjectRoot is outside the project root \u2014 using watched dirPath instead",
|
|
@@ -8304,7 +8488,7 @@ var plugin24 = {
|
|
|
8304
8488
|
}
|
|
8305
8489
|
const events = input["events"] ?? ["change", "add", "delete"];
|
|
8306
8490
|
const recursive = input["recursive"] ?? true;
|
|
8307
|
-
const bad = paths.find((p) => !
|
|
8491
|
+
const bad = paths.find((p) => !withinProject(p));
|
|
8308
8492
|
if (bad !== void 0) {
|
|
8309
8493
|
return {
|
|
8310
8494
|
ok: false,
|
|
@@ -8549,6 +8733,7 @@ async function formatFile(filePath, timeoutMs) {
|
|
|
8549
8733
|
}
|
|
8550
8734
|
const formatter = resolveFormatter();
|
|
8551
8735
|
if (!formatter) return null;
|
|
8736
|
+
const started = Date.now();
|
|
8552
8737
|
let bytesBefore;
|
|
8553
8738
|
try {
|
|
8554
8739
|
bytesBefore = (await stat4(filePath)).size;
|
|
@@ -8593,14 +8778,15 @@ async function formatFile(filePath, timeoutMs) {
|
|
|
8593
8778
|
} catch {
|
|
8594
8779
|
return null;
|
|
8595
8780
|
}
|
|
8781
|
+
const durationMs = Date.now() - started;
|
|
8596
8782
|
if (bytesAfter !== bytesBefore) {
|
|
8597
|
-
return { changed: true, bytesBefore, bytesAfter };
|
|
8783
|
+
return { changed: true, bytesBefore, bytesAfter, durationMs };
|
|
8598
8784
|
}
|
|
8599
8785
|
const hashAfter = await sha256File(filePath);
|
|
8600
8786
|
if (hashBefore === null || hashAfter === null) {
|
|
8601
|
-
return { changed: false, bytesBefore, bytesAfter };
|
|
8787
|
+
return { changed: false, bytesBefore, bytesAfter, durationMs };
|
|
8602
8788
|
}
|
|
8603
|
-
return { changed: hashBefore !== hashAfter, bytesBefore, bytesAfter };
|
|
8789
|
+
return { changed: hashBefore !== hashAfter, bytesBefore, bytesAfter, durationMs };
|
|
8604
8790
|
}
|
|
8605
8791
|
var plugin25 = {
|
|
8606
8792
|
name: "format-on-save",
|
|
@@ -8689,6 +8875,7 @@ var plugin25 = {
|
|
|
8689
8875
|
changed: result.changed,
|
|
8690
8876
|
bytesBefore: result.bytesBefore,
|
|
8691
8877
|
bytesAfter: result.bytesAfter,
|
|
8878
|
+
durationMs: result.durationMs,
|
|
8692
8879
|
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
8693
8880
|
};
|
|
8694
8881
|
if (result.changed) {
|
|
@@ -8743,7 +8930,10 @@ var plugin25 = {
|
|
|
8743
8930
|
formatted: state23.formattedCount,
|
|
8744
8931
|
clean: state23.cleanCount,
|
|
8745
8932
|
errors: state23.errorCount,
|
|
8746
|
-
coveredSkips: state23.coveredSkipCount
|
|
8933
|
+
coveredSkips: state23.coveredSkipCount,
|
|
8934
|
+
bytesBefore: state23.lastResult?.bytesBefore ?? 0,
|
|
8935
|
+
bytesAfter: state23.lastResult?.bytesAfter ?? 0,
|
|
8936
|
+
durationMs: state23.lastResult?.durationMs ?? 0
|
|
8747
8937
|
},
|
|
8748
8938
|
lastResult: state23.lastResult
|
|
8749
8939
|
};
|
|
@@ -9316,7 +9506,7 @@ var git_autocommit_default = plugin26;
|
|
|
9316
9506
|
|
|
9317
9507
|
// src/gitignore-guard/index.ts
|
|
9318
9508
|
import { access as access3, readFile as readFile9, writeFile as writeFile2 } from "node:fs/promises";
|
|
9319
|
-
import { basename as basename4, isAbsolute as
|
|
9509
|
+
import { basename as basename4, isAbsolute as isAbsolute12, join as join4, relative as relative13, resolve as resolve13, sep as sep3 } from "node:path";
|
|
9320
9510
|
var API_VERSION19 = "^0.1.10";
|
|
9321
9511
|
var DEFAULT_ARTIFACT_PATTERNS = Object.freeze([
|
|
9322
9512
|
"dist/",
|
|
@@ -9438,9 +9628,9 @@ async function appendPatterns(target, patterns, limit) {
|
|
|
9438
9628
|
}
|
|
9439
9629
|
function projectRelativePath(rawPath, cwd) {
|
|
9440
9630
|
const root = cwd ?? process.cwd();
|
|
9441
|
-
const abs =
|
|
9442
|
-
const rel = toForwardSlashes(
|
|
9443
|
-
if (rel.startsWith("..") ||
|
|
9631
|
+
const abs = isAbsolute12(rawPath) ? rawPath : resolve13(root, rawPath);
|
|
9632
|
+
const rel = toForwardSlashes(relative13(root, abs));
|
|
9633
|
+
if (rel.startsWith("..") || isAbsolute12(rel) || rel === "") return null;
|
|
9444
9634
|
return { abs, rel, root };
|
|
9445
9635
|
}
|
|
9446
9636
|
var DEFAULTS21 = {
|
|
@@ -9752,7 +9942,7 @@ var gitignore_guard_default = plugin27;
|
|
|
9752
9942
|
// src/import-organizer/index.ts
|
|
9753
9943
|
import { spawn } from "node:child_process";
|
|
9754
9944
|
import { existsSync as existsSync3, statSync as statSync2 } from "node:fs";
|
|
9755
|
-
import { basename as basename5, isAbsolute as
|
|
9945
|
+
import { basename as basename5, isAbsolute as isAbsolute13 } from "node:path";
|
|
9756
9946
|
var ALLOWED_FIRST_TOKENS = /* @__PURE__ */ new Set([
|
|
9757
9947
|
"npx",
|
|
9758
9948
|
"pnpm",
|
|
@@ -9795,17 +9985,31 @@ function resolveAllowedCommand(command) {
|
|
|
9795
9985
|
const tokens = command.split(/\s+/).filter(Boolean);
|
|
9796
9986
|
if (tokens.length === 0) return null;
|
|
9797
9987
|
const head = tokens[0];
|
|
9988
|
+
if (PACKAGE_RUNNERS.has(head)) {
|
|
9989
|
+
let i = 1;
|
|
9990
|
+
while (i < tokens.length && (tokens[i] === "exec" || tokens[i] === "dlx" || tokens[i] === "run")) {
|
|
9991
|
+
i++;
|
|
9992
|
+
}
|
|
9993
|
+
const toolToken = tokens[i];
|
|
9994
|
+
if (!toolToken || !(toolToken in LOCAL_BIN_PACKAGES)) return null;
|
|
9995
|
+
}
|
|
9996
|
+
if (head === "node") {
|
|
9997
|
+
const target = tokens[1];
|
|
9998
|
+
if (!target) return null;
|
|
9999
|
+
const base = basename5(target);
|
|
10000
|
+
if (!(base in LOCAL_BIN_PACKAGES) && !ALLOWED_FIRST_TOKENS.has(base)) return null;
|
|
10001
|
+
}
|
|
9798
10002
|
const local = ALLOWED_FIRST_TOKENS.has(head) ? resolveLocalToolCommand(tokens) : null;
|
|
9799
10003
|
if (local) return local;
|
|
9800
10004
|
if (ALLOWED_FIRST_TOKENS.has(head)) {
|
|
9801
10005
|
return { cmd: head, args: tokens.slice(1) };
|
|
9802
10006
|
}
|
|
9803
|
-
if (
|
|
10007
|
+
if (isAbsolute13(head)) {
|
|
9804
10008
|
if (!withinProject(head)) return null;
|
|
9805
10009
|
const base = basename5(head);
|
|
9806
10010
|
if (ALLOWED_FIRST_TOKENS.has(base)) return { cmd: head, args: tokens.slice(1) };
|
|
9807
10011
|
}
|
|
9808
|
-
if (!
|
|
10012
|
+
if (!isAbsolute13(head) && withinProject(head)) {
|
|
9809
10013
|
const base = basename5(head);
|
|
9810
10014
|
if (ALLOWED_FIRST_TOKENS.has(base)) return { cmd: head, args: tokens.slice(1) };
|
|
9811
10015
|
}
|
|
@@ -10032,6 +10236,12 @@ var plugin28 = {
|
|
|
10032
10236
|
bytesAfter: result.bytesAfter,
|
|
10033
10237
|
when: (/* @__PURE__ */ new Date()).toISOString()
|
|
10034
10238
|
};
|
|
10239
|
+
if (/\boxlint\b/.test(result.command)) {
|
|
10240
|
+
return {
|
|
10241
|
+
additionalContext: `
|
|
10242
|
+
\u{1F4E6} import-organizer: oxlint has no import-organize support \u2014 ran '${result.command}' as a no-op for import sorting on '${filePath}'.`
|
|
10243
|
+
};
|
|
10244
|
+
}
|
|
10035
10245
|
if (result.changed) {
|
|
10036
10246
|
state25.organizedCount += 1;
|
|
10037
10247
|
const delta = result.bytesAfter - result.bytesBefore;
|
|
@@ -10088,6 +10298,7 @@ ${result.stderr.trim()}`
|
|
|
10088
10298
|
});
|
|
10089
10299
|
},
|
|
10090
10300
|
teardown(api) {
|
|
10301
|
+
clearLocalBinCache();
|
|
10091
10302
|
if (state25.hookUnregister) {
|
|
10092
10303
|
try {
|
|
10093
10304
|
state25.hookUnregister();
|
|
@@ -10342,7 +10553,7 @@ var injection_shield_default = plugin29;
|
|
|
10342
10553
|
|
|
10343
10554
|
// src/interface-contract-guard/index.ts
|
|
10344
10555
|
import { readFile as readFile10 } from "node:fs/promises";
|
|
10345
|
-
import { isAbsolute as
|
|
10556
|
+
import { isAbsolute as isAbsolute14, relative as relative14, resolve as resolve14 } from "node:path";
|
|
10346
10557
|
var API_VERSION21 = "^0.1.10";
|
|
10347
10558
|
var state27 = {
|
|
10348
10559
|
scanCount: 0,
|
|
@@ -10375,7 +10586,7 @@ function toPosix5(p) {
|
|
|
10375
10586
|
return p.replace(/\\/g, "/");
|
|
10376
10587
|
}
|
|
10377
10588
|
function relativePath5(p) {
|
|
10378
|
-
return toPosix5(
|
|
10589
|
+
return toPosix5(relative14(process.cwd(), p));
|
|
10379
10590
|
}
|
|
10380
10591
|
var INTERFACE_RE = /(?:export\s+)?interface\s+([A-Za-z_$][A-Za-z0-9_$]*)/g;
|
|
10381
10592
|
function extractInterfaceNames(content) {
|
|
@@ -10403,7 +10614,7 @@ function collectImplementedNames(content, into) {
|
|
|
10403
10614
|
}
|
|
10404
10615
|
async function scanPath3(rawPath, cfg) {
|
|
10405
10616
|
const root = process.cwd();
|
|
10406
|
-
const resolved =
|
|
10617
|
+
const resolved = isAbsolute14(rawPath) ? resolve14(rawPath) : resolve14(root, rawPath);
|
|
10407
10618
|
const exts = normalizeExtensions4(cfg.extensions);
|
|
10408
10619
|
const allFiles = await collectSourceFilesAsync(resolved, { extensions: exts });
|
|
10409
10620
|
const files = allFiles.slice(0, cfg.maxFiles);
|
|
@@ -10493,7 +10704,7 @@ var plugin30 = {
|
|
|
10493
10704
|
const exts = normalizeExtensions4(cfg.extensions);
|
|
10494
10705
|
if (!matchesExtension(sourcePath, exts)) return;
|
|
10495
10706
|
state27.hookInvocationCount += 1;
|
|
10496
|
-
const resolved =
|
|
10707
|
+
const resolved = resolve14(process.cwd(), sourcePath);
|
|
10497
10708
|
let content;
|
|
10498
10709
|
try {
|
|
10499
10710
|
content = await readFile10(resolved, "utf-8");
|
|
@@ -10541,7 +10752,7 @@ If you changed member shapes, search the project for implementers/\`satisfies\`/
|
|
|
10541
10752
|
state27.findingCount += result.findings.length;
|
|
10542
10753
|
return {
|
|
10543
10754
|
ok: true,
|
|
10544
|
-
path: relativePath5(
|
|
10755
|
+
path: relativePath5(resolve14(process.cwd(), rawPath)),
|
|
10545
10756
|
scannedFiles: result.scannedFiles,
|
|
10546
10757
|
findings: result.findings,
|
|
10547
10758
|
// Say so when the corpus was cut short. A partial scan that
|
|
@@ -10622,7 +10833,7 @@ var interface_contract_guard_default = plugin30;
|
|
|
10622
10833
|
|
|
10623
10834
|
// src/knowledge-graph/index.ts
|
|
10624
10835
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
10625
|
-
import { dirname as dirname5, isAbsolute as
|
|
10836
|
+
import { dirname as dirname5, isAbsolute as isAbsolute15, relative as relative15, resolve as resolve15 } from "node:path";
|
|
10626
10837
|
import { atomicWrite as atomicWrite2, ensureDir as ensureDir2 } from "@wrongstack/core/utils";
|
|
10627
10838
|
var API_VERSION22 = "^0.1.10";
|
|
10628
10839
|
var state28 = {
|
|
@@ -10644,10 +10855,10 @@ var DEFAULTS25 = {
|
|
|
10644
10855
|
};
|
|
10645
10856
|
function resolveProjectPath5(rawPath, cwd = process.cwd()) {
|
|
10646
10857
|
if (typeof rawPath !== "string" || rawPath.length === 0) return null;
|
|
10647
|
-
const root =
|
|
10648
|
-
const resolved =
|
|
10649
|
-
const rel =
|
|
10650
|
-
if (rel === "" || !rel.startsWith("..") && !
|
|
10858
|
+
const root = resolve15(cwd);
|
|
10859
|
+
const resolved = isAbsolute15(rawPath) ? resolve15(rawPath) : resolve15(root, rawPath);
|
|
10860
|
+
const rel = relative15(root, resolved);
|
|
10861
|
+
if (rel === "" || !rel.startsWith("..") && !isAbsolute15(rel)) return resolved;
|
|
10651
10862
|
return null;
|
|
10652
10863
|
}
|
|
10653
10864
|
function readConfig26(raw) {
|
|
@@ -10946,7 +11157,7 @@ var knowledge_graph_default = plugin31;
|
|
|
10946
11157
|
|
|
10947
11158
|
// src/license-audit-gate/index.ts
|
|
10948
11159
|
import { readFileSync as readFileSync8 } from "node:fs";
|
|
10949
|
-
import { resolve as
|
|
11160
|
+
import { resolve as resolve16 } from "node:path";
|
|
10950
11161
|
var API_VERSION23 = "^0.1.10";
|
|
10951
11162
|
var state29 = {
|
|
10952
11163
|
invocations: 0,
|
|
@@ -10996,24 +11207,10 @@ function extractLicenseStrings(pkg) {
|
|
|
10996
11207
|
}
|
|
10997
11208
|
return [...new Set(out)];
|
|
10998
11209
|
}
|
|
10999
|
-
var INSTALL_RE3 = /(?:^|[;&|]\s*)(npm|pnpm|yarn|bun)\s+(?:install|i|add)\s+([^;&|]+)/gi;
|
|
11000
11210
|
function parsePackageNames(command) {
|
|
11001
|
-
|
|
11002
|
-
|
|
11003
|
-
|
|
11004
|
-
const argString = m[2] ?? "";
|
|
11005
|
-
for (const token of argString.split(/\s+/)) {
|
|
11006
|
-
if (!token || token.startsWith("-")) continue;
|
|
11007
|
-
if (/^(\.|\/|file:|git\+|https?:)/i.test(token) || token.endsWith(".tgz")) continue;
|
|
11008
|
-
const cleaned = token.replace(/^['"]|['"]$/g, "");
|
|
11009
|
-
if (!cleaned) continue;
|
|
11010
|
-
let name = cleaned;
|
|
11011
|
-
const at = cleaned.lastIndexOf("@");
|
|
11012
|
-
if (at > 0) name = cleaned.slice(0, at);
|
|
11013
|
-
if (name) names.push(name);
|
|
11014
|
-
}
|
|
11015
|
-
}
|
|
11016
|
-
return [...new Set(names)];
|
|
11211
|
+
return [
|
|
11212
|
+
...new Set(parseInstallCommands(command).flatMap((entry) => entry.packages.map((pkg) => pkg.name)))
|
|
11213
|
+
];
|
|
11017
11214
|
}
|
|
11018
11215
|
function auditPackages(names, allowedLicenses) {
|
|
11019
11216
|
const results = [];
|
|
@@ -11022,7 +11219,7 @@ function auditPackages(names, allowedLicenses) {
|
|
|
11022
11219
|
for (const name of names) {
|
|
11023
11220
|
let licenses = [];
|
|
11024
11221
|
try {
|
|
11025
|
-
const pkgPath =
|
|
11222
|
+
const pkgPath = resolve16("node_modules", name, "package.json");
|
|
11026
11223
|
const raw = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
11027
11224
|
licenses = extractLicenseStrings(raw);
|
|
11028
11225
|
} catch {
|
|
@@ -11207,7 +11404,7 @@ import { readFileSync as readFileSync9 } from "node:fs";
|
|
|
11207
11404
|
import { mkdtemp, readFile as readFile11, rm, writeFile as writeFile3 } from "node:fs/promises";
|
|
11208
11405
|
import { createRequire as createRequire2 } from "node:module";
|
|
11209
11406
|
import { tmpdir } from "node:os";
|
|
11210
|
-
import { dirname as dirname6, isAbsolute as
|
|
11407
|
+
import { dirname as dirname6, isAbsolute as isAbsolute16, join as join5, relative as relative16, resolve as resolve17, sep as sep4 } from "node:path";
|
|
11211
11408
|
var API_VERSION24 = "^0.1.10";
|
|
11212
11409
|
var state30 = {
|
|
11213
11410
|
/** Total PreToolUse invocations. */
|
|
@@ -11247,19 +11444,19 @@ var LINTER_PACKAGES = {
|
|
|
11247
11444
|
};
|
|
11248
11445
|
var linterCache = new BoundedMap({ max: 32, ttlMs: 3e5 });
|
|
11249
11446
|
function isInside2(parent, candidate) {
|
|
11250
|
-
const rel =
|
|
11251
|
-
return rel === "" || !rel.startsWith(`..${sep4}`) && rel !== ".." && !
|
|
11447
|
+
const rel = relative16(parent, candidate);
|
|
11448
|
+
return rel === "" || !rel.startsWith(`..${sep4}`) && rel !== ".." && !isAbsolute16(rel);
|
|
11252
11449
|
}
|
|
11253
11450
|
function resolveLocalLinter(name, cwd) {
|
|
11254
11451
|
try {
|
|
11255
11452
|
const packageName = LINTER_PACKAGES[name];
|
|
11256
|
-
const requireFromProject = createRequire2(
|
|
11453
|
+
const requireFromProject = createRequire2(resolve17(cwd, "package.json"));
|
|
11257
11454
|
const packagePath = requireFromProject.resolve(`${packageName}/package.json`);
|
|
11258
11455
|
const packageJson = JSON.parse(readFileSync9(packagePath, "utf-8"));
|
|
11259
11456
|
const relativeBin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.[name] ?? Object.values(packageJson.bin ?? {})[0];
|
|
11260
|
-
if (!relativeBin ||
|
|
11457
|
+
if (!relativeBin || isAbsolute16(relativeBin)) return null;
|
|
11261
11458
|
const packageDir = dirname6(packagePath);
|
|
11262
|
-
const entry =
|
|
11459
|
+
const entry = resolve17(packageDir, relativeBin);
|
|
11263
11460
|
if (!isInside2(packageDir, entry)) return null;
|
|
11264
11461
|
return {
|
|
11265
11462
|
cmd: process.execPath,
|
|
@@ -11311,9 +11508,10 @@ async function detectLinter(requested, cwd) {
|
|
|
11311
11508
|
}
|
|
11312
11509
|
async function lintContent(content, filePath, linter, timeoutMs, cwd, signal) {
|
|
11313
11510
|
const ext = filePath.includes(".") ? filePath.slice(filePath.lastIndexOf(".")) : ".ts";
|
|
11314
|
-
|
|
11315
|
-
const tmpFile = join5(tmpDir, `input${ext}`);
|
|
11511
|
+
let tmpDir;
|
|
11316
11512
|
try {
|
|
11513
|
+
tmpDir = await mkdtemp(join5(tmpdir(), "lint-gate-"));
|
|
11514
|
+
const tmpFile = join5(tmpDir, `input${ext}`);
|
|
11317
11515
|
await writeFile3(tmpFile, content, "utf-8");
|
|
11318
11516
|
const fullArgs = [...linter.args, tmpFile];
|
|
11319
11517
|
const result = await runCommand2(linter.cmd, fullArgs, timeoutMs, cwd, signal);
|
|
@@ -11324,14 +11522,15 @@ async function lintContent(content, filePath, linter, timeoutMs, cwd, signal) {
|
|
|
11324
11522
|
if (signal.aborted) throw signal.reason;
|
|
11325
11523
|
return null;
|
|
11326
11524
|
} finally {
|
|
11327
|
-
await rm(tmpDir, { recursive: true, force: true }).catch(() => void 0);
|
|
11525
|
+
if (tmpDir) await rm(tmpDir, { recursive: true, force: true }).catch(() => void 0);
|
|
11328
11526
|
}
|
|
11329
11527
|
}
|
|
11330
11528
|
async function lintAndFix(content, filePath, linter, timeoutMs, cwd, signal) {
|
|
11331
11529
|
const ext = filePath.includes(".") ? filePath.slice(filePath.lastIndexOf(".")) : ".ts";
|
|
11332
|
-
|
|
11333
|
-
const tmpFile = join5(tmpDir, `input${ext}`);
|
|
11530
|
+
let tmpDir;
|
|
11334
11531
|
try {
|
|
11532
|
+
tmpDir = await mkdtemp(join5(tmpdir(), "lint-gate-fix-"));
|
|
11533
|
+
const tmpFile = join5(tmpDir, `input${ext}`);
|
|
11335
11534
|
await writeFile3(tmpFile, content, "utf-8");
|
|
11336
11535
|
const fixArgs = linter.name === "biome" ? [linter.args[0], "check", "--write", tmpFile] : [linter.args[0], "--fix", tmpFile];
|
|
11337
11536
|
await runCommand2(linter.cmd, fixArgs, timeoutMs, cwd, signal);
|
|
@@ -11341,7 +11540,7 @@ async function lintAndFix(content, filePath, linter, timeoutMs, cwd, signal) {
|
|
|
11341
11540
|
if (signal.aborted) throw signal.reason;
|
|
11342
11541
|
return content;
|
|
11343
11542
|
} finally {
|
|
11344
|
-
await rm(tmpDir, { recursive: true, force: true }).catch(() => void 0);
|
|
11543
|
+
if (tmpDir) await rm(tmpDir, { recursive: true, force: true }).catch(() => void 0);
|
|
11345
11544
|
}
|
|
11346
11545
|
}
|
|
11347
11546
|
function parseLinterOutput(stdout, linterName) {
|
|
@@ -11436,7 +11635,7 @@ var plugin33 = {
|
|
|
11436
11635
|
state30.lastResult = null;
|
|
11437
11636
|
linterCache.clear();
|
|
11438
11637
|
const cfg = readConfig28(api.config.extensions?.["lint-gate"]);
|
|
11439
|
-
const cwd =
|
|
11638
|
+
const cwd = resolve17(api.config.cwd ?? process.cwd());
|
|
11440
11639
|
const linterReady = detectLinter(cfg.linter, cwd).then((linter) => {
|
|
11441
11640
|
if (!linter) {
|
|
11442
11641
|
api.log.warn("lint-gate: no linter found (biome or eslint) \u2014 hook will be a no-op", {
|
|
@@ -11595,9 +11794,9 @@ ${summary}${truncated}`
|
|
|
11595
11794
|
name: "lint-gate",
|
|
11596
11795
|
stage: "mutate",
|
|
11597
11796
|
timeoutMs: Math.max(1e3, cfg.timeoutMs + 1e3),
|
|
11598
|
-
//
|
|
11599
|
-
//
|
|
11600
|
-
failurePolicy: "
|
|
11797
|
+
// Fail closed: a linter crash or timeout must not let unlinted
|
|
11798
|
+
// content through in block mode (issue #363).
|
|
11799
|
+
failurePolicy: "closed"
|
|
11601
11800
|
});
|
|
11602
11801
|
api.tools.register({
|
|
11603
11802
|
name: "lint_gate_status",
|
|
@@ -11753,6 +11952,12 @@ var plugin34 = {
|
|
|
11753
11952
|
description: "Caches identical provider requests and short-circuits the provider call on a hit (wrapProviderRunner). Opt-in; deterministic-only by default.",
|
|
11754
11953
|
apiVersion: "^0.1.10",
|
|
11755
11954
|
capabilities: { tools: true },
|
|
11955
|
+
// Wrap-stack contract (issue #362): ExtensionRegistry composes wrappers
|
|
11956
|
+
// first-registered = outermost, and both dependsOn and optionalDeps load
|
|
11957
|
+
// dependencies first. Declaring prompt-firewall here guarantees the
|
|
11958
|
+
// firewall wraps OUTSIDE this cache even if the manifest order changes —
|
|
11959
|
+
// otherwise a cache hit short-circuits `inner` and bypasses redaction.
|
|
11960
|
+
optionalDeps: ["prompt-firewall"],
|
|
11756
11961
|
defaultConfig: { ...DEFAULTS28 },
|
|
11757
11962
|
configSchema: {
|
|
11758
11963
|
type: "object",
|
|
@@ -11933,7 +12138,7 @@ var llm_cache_default = plugin34;
|
|
|
11933
12138
|
|
|
11934
12139
|
// src/loop-breaker/index.ts
|
|
11935
12140
|
import { execFile as execFile9 } from "node:child_process";
|
|
11936
|
-
import { isAbsolute as
|
|
12141
|
+
import { isAbsolute as isAbsolute17, relative as relative17 } from "node:path";
|
|
11937
12142
|
var state32 = {
|
|
11938
12143
|
lastFingerprint: null,
|
|
11939
12144
|
streak: 0,
|
|
@@ -12044,7 +12249,7 @@ function hashString2(value) {
|
|
|
12044
12249
|
return String(h >>> 0);
|
|
12045
12250
|
}
|
|
12046
12251
|
async function gitDiffFingerprint(cwd, targetPath, signal) {
|
|
12047
|
-
const pathspec =
|
|
12252
|
+
const pathspec = isAbsolute17(targetPath) ? relative17(cwd, targetPath) : targetPath;
|
|
12048
12253
|
if (!pathspec || pathspec === ".." || pathspec.startsWith("../") || pathspec.startsWith("..\\")) {
|
|
12049
12254
|
return null;
|
|
12050
12255
|
}
|
|
@@ -13579,6 +13784,29 @@ var plugin38 = {
|
|
|
13579
13784
|
var notify_hub_default = plugin38;
|
|
13580
13785
|
|
|
13581
13786
|
// src/path-guard/glob.ts
|
|
13787
|
+
var GLOB_REDOS_BUDGET_MS = 250;
|
|
13788
|
+
function mergeGuardedRegex(patterns) {
|
|
13789
|
+
const flags = /* @__PURE__ */ new Set();
|
|
13790
|
+
for (const p of patterns) {
|
|
13791
|
+
for (const f of p.flags) {
|
|
13792
|
+
if (f !== "g" && f !== "y") flags.add(f);
|
|
13793
|
+
}
|
|
13794
|
+
}
|
|
13795
|
+
const uniqueFlags = [...flags].join("");
|
|
13796
|
+
return new RegExp(patterns.map((p) => p.source).join("|"), uniqueFlags);
|
|
13797
|
+
}
|
|
13798
|
+
async function matchesAnyGuarded(path, patterns, options = {}) {
|
|
13799
|
+
const normalized = normalizePath2(path);
|
|
13800
|
+
if (patterns.length === 0) return false;
|
|
13801
|
+
const result = await withReDoSGuard(
|
|
13802
|
+
mergeGuardedRegex(patterns),
|
|
13803
|
+
normalized,
|
|
13804
|
+
options.budgetMs ?? GLOB_REDOS_BUDGET_MS,
|
|
13805
|
+
options.onTimeout ? { onTimeout: options.onTimeout } : {}
|
|
13806
|
+
);
|
|
13807
|
+
if (result.timedOut) return true;
|
|
13808
|
+
return result.match !== null;
|
|
13809
|
+
}
|
|
13582
13810
|
function compilePathGlob(pattern) {
|
|
13583
13811
|
const normalized = pattern.replace(/\\/g, "/");
|
|
13584
13812
|
let source = "";
|
|
@@ -13713,8 +13941,7 @@ function scopesMayOverlap(left, right) {
|
|
|
13713
13941
|
}
|
|
13714
13942
|
return hasPartialSegmentWildcard(left) && rightPrefix.startsWith(leftPrefix) || hasPartialSegmentWildcard(right) && leftPrefix.startsWith(rightPrefix);
|
|
13715
13943
|
}
|
|
13716
|
-
function
|
|
13717
|
-
if (matchesAny(target.path, patterns)) return true;
|
|
13944
|
+
function targetIntersectsScope(target, patternTexts) {
|
|
13718
13945
|
if (target.kind === "file") return false;
|
|
13719
13946
|
const normalized = normalizePath2(target.path).replace(/\/$/, "");
|
|
13720
13947
|
if (!isUnresolvedPathScope(normalized)) {
|
|
@@ -13726,6 +13953,14 @@ function targetIntersectsPatterns(target, patternTexts, patterns) {
|
|
|
13726
13953
|
}
|
|
13727
13954
|
return patternTexts.some((pattern) => scopesMayOverlap(normalized, normalizePath2(pattern)));
|
|
13728
13955
|
}
|
|
13956
|
+
function targetIntersectsPatterns(target, patternTexts, patterns) {
|
|
13957
|
+
if (matchesAny(target.path, patterns)) return true;
|
|
13958
|
+
return targetIntersectsScope(target, patternTexts);
|
|
13959
|
+
}
|
|
13960
|
+
async function targetIntersectsPatternsGuarded(target, patternTexts, patterns, options = {}) {
|
|
13961
|
+
if (await matchesAnyGuarded(target.path, patterns, options)) return true;
|
|
13962
|
+
return targetIntersectsScope(target, patternTexts);
|
|
13963
|
+
}
|
|
13729
13964
|
function targetFullyAllowed(target, allowTexts, allowRes) {
|
|
13730
13965
|
if (target.kind === "file") return matchesAny(target.path, allowRes);
|
|
13731
13966
|
const normalized = normalizePath2(target.path).replace(/\/$/, "");
|
|
@@ -14042,7 +14277,7 @@ function commandRecursivelyDeletes(command) {
|
|
|
14042
14277
|
)) {
|
|
14043
14278
|
return true;
|
|
14044
14279
|
}
|
|
14045
|
-
const destructive = /(?:^|[;&|\r\n]\s*|\{\s*|\(\s*|\bxargs(?:\s+-[^\s]+)*\s+)(rm|rmdir|del)\s+((?:"[^"]*"|'[^']*'|\\.|\{[^}]*\}|\([^()]*\)|[^;&|\r\n}])+)/gi;
|
|
14280
|
+
const destructive = /(?:^|[;&|\r\n]\s*|\{\s*|\(\s*|\bxargs(?:\s+-[^\s]+)*\s+)(rm|rmdir|del|rd|Remove-Item)\s+((?:"[^"]*"|'[^']*'|\\.|\{[^}]*\}|\([^()]*\)|[^;&|\r\n}])+)/gi;
|
|
14046
14281
|
let match = destructive.exec(stripped);
|
|
14047
14282
|
while (match !== null) {
|
|
14048
14283
|
const tool = match[1]?.toLowerCase();
|
|
@@ -14055,6 +14290,8 @@ function commandRecursivelyDeletes(command) {
|
|
|
14055
14290
|
if (token === "--") break;
|
|
14056
14291
|
if (tool === "rm") {
|
|
14057
14292
|
if (token === "--recursive" || /^-[^-]*[rR]/.test(token)) recursive = true;
|
|
14293
|
+
} else if (tool === "remove-item") {
|
|
14294
|
+
if (/^-Recurse$/i.test(token) || /^-r$/i.test(token)) recursive = true;
|
|
14058
14295
|
} else if (/^\/[a-z]*s[a-z]*$/i.test(token)) {
|
|
14059
14296
|
recursive = true;
|
|
14060
14297
|
}
|
|
@@ -14303,7 +14540,7 @@ function destructiveTargetsAtDepth(command, depth) {
|
|
|
14303
14540
|
}
|
|
14304
14541
|
targets.push(...destructiveTargetsAtDepth(executableBody, depth + 1));
|
|
14305
14542
|
}
|
|
14306
|
-
const destructive = /(?:^|[;&|\r\n]\s*|\{\s*|(?<![$(])\(\s*|\bxargs(?:\s+-[^\s]+)*\s+)(?:sudo\s+)?(rm|rmdir|del|unlink|truncate|shred|mv)\s+((?:"[^"]*"|'[^']*'|\\.|\{[^}]*\}|\([^()]*\)|[^;&|\r\n}])+)/gi;
|
|
14543
|
+
const destructive = /(?:^|[;&|\r\n]\s*|\{\s*|(?<![$(])\(\s*|\bxargs(?:\s+-[^\s]+)*\s+)(?:sudo\s+)?(rm|rmdir|del|rd|Remove-Item|unlink|truncate|shred|mv)\s+((?:"[^"]*"|'[^']*'|\\.|\{[^}]*\}|\([^()]*\)|[^;&|\r\n}])+)/gi;
|
|
14307
14544
|
let m = destructive.exec(normalizedCommand);
|
|
14308
14545
|
while (m !== null) {
|
|
14309
14546
|
if (!tokenIsQuoted(m, m[1] ?? "")) targets.push(...shellArgs(m[2] ?? ""));
|
|
@@ -14791,8 +15028,27 @@ function operationLabel(toolName) {
|
|
|
14791
15028
|
}
|
|
14792
15029
|
|
|
14793
15030
|
// src/path-guard/index.ts
|
|
15031
|
+
import { realpathSync } from "node:fs";
|
|
15032
|
+
import { resolve as resolve18 } from "node:path";
|
|
15033
|
+
function isSymlinkEscape(path, cwd) {
|
|
15034
|
+
if (!withinProject(path)) return false;
|
|
15035
|
+
try {
|
|
15036
|
+
const abs = resolve18(cwd ?? process.cwd(), path);
|
|
15037
|
+
const real = realpathSync(abs);
|
|
15038
|
+
return !withinProject(real);
|
|
15039
|
+
} catch {
|
|
15040
|
+
return false;
|
|
15041
|
+
}
|
|
15042
|
+
}
|
|
14794
15043
|
function createState() {
|
|
14795
|
-
return {
|
|
15044
|
+
return {
|
|
15045
|
+
invocations: 0,
|
|
15046
|
+
blocks: 0,
|
|
15047
|
+
warns: 0,
|
|
15048
|
+
redosTimeouts: 0,
|
|
15049
|
+
lastBlock: null,
|
|
15050
|
+
hookUnregister: null
|
|
15051
|
+
};
|
|
14796
15052
|
}
|
|
14797
15053
|
var states = /* @__PURE__ */ new WeakMap();
|
|
14798
15054
|
var latestState = createState();
|
|
@@ -14887,7 +15143,11 @@ var plugin39 = {
|
|
|
14887
15143
|
additionalContext: `path-guard (warn mode): ${subject} and this ${operation} would modify it. Double-check this is intentional.`
|
|
14888
15144
|
};
|
|
14889
15145
|
};
|
|
14890
|
-
const
|
|
15146
|
+
const bumpRedos = () => {
|
|
15147
|
+
state60.redosTimeouts += 1;
|
|
15148
|
+
api.metrics.counter("redos_timeouts");
|
|
15149
|
+
};
|
|
15150
|
+
const hook = async (input) => {
|
|
14891
15151
|
if (!cfg.enabled) return;
|
|
14892
15152
|
state60.invocations += 1;
|
|
14893
15153
|
const toolName = input.toolName ?? "";
|
|
@@ -14908,21 +15168,27 @@ var plugin39 = {
|
|
|
14908
15168
|
const effectiveCwd = effectiveToolCwd(ti["cwd"], input.cwd);
|
|
14909
15169
|
const recursivelyDeletes = commandRecursivelyDeletes(commandForInspection);
|
|
14910
15170
|
const deletesImplicitScope = commandDeletesImplicitScope(commandForInspection);
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
|
|
14914
|
-
|
|
14915
|
-
|
|
14916
|
-
|
|
14917
|
-
|
|
14918
|
-
|
|
14919
|
-
|
|
14920
|
-
|
|
14921
|
-
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14925
|
-
|
|
15171
|
+
const shellHits = await Promise.all(
|
|
15172
|
+
shellTargets.map(async (path) => {
|
|
15173
|
+
const target = {
|
|
15174
|
+
path: relativeToInvocationCwd(resolveTargetPath(path, effectiveCwd), input.cwd),
|
|
15175
|
+
kind: isRootPathScope(path) && deletesImplicitScope || isUnresolvedPathScope(path) || recursivelyDeletes && (isDirectoryAmbiguousPath(path) || hasConfiguredProtectedDescendant(path, cfg.protect)) ? "deletion-scope" : "file"
|
|
15176
|
+
};
|
|
15177
|
+
if (targetFullyAllowed(target, cfg.allow, allowRes)) return null;
|
|
15178
|
+
const protectedShellTarget = await targetIntersectsPatternsGuarded(target, cfg.protect, protectRes, { onTimeout: bumpRedos }) || await matchesAnyGuarded(`${target.path.replace(/\/$/, "")}/.path-guard-probe`, protectRes, {
|
|
15179
|
+
onTimeout: bumpRedos
|
|
15180
|
+
});
|
|
15181
|
+
return protectedShellTarget ? target : null;
|
|
15182
|
+
})
|
|
15183
|
+
);
|
|
15184
|
+
const firstProtected = shellHits.find((t) => t !== null);
|
|
15185
|
+
if (firstProtected) {
|
|
15186
|
+
return verdict(
|
|
15187
|
+
firstProtected.path,
|
|
15188
|
+
toolName,
|
|
15189
|
+
"destructive shell command",
|
|
15190
|
+
firstProtected.kind !== "file"
|
|
15191
|
+
);
|
|
14926
15192
|
}
|
|
14927
15193
|
const writes = writesToDisk({ ...input, toolInput: ti });
|
|
14928
15194
|
const hasStructuredTarget = [...PATH_FIELDS, ...PATH_LIST_FIELDS].some((field) => ti[field] !== void 0) || typeof ti["patch"] === "string";
|
|
@@ -14933,11 +15199,21 @@ var plugin39 = {
|
|
|
14933
15199
|
}
|
|
14934
15200
|
if (!writesToDisk({ ...input, toolInput: ti }) || isReadOnlyInvocation(toolName, ti)) return;
|
|
14935
15201
|
const targets = pathsFromToolInput(ti, toolName, input.cwd);
|
|
14936
|
-
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
15202
|
+
const writeHits = await Promise.all(
|
|
15203
|
+
targets.map(async (target) => {
|
|
15204
|
+
if (target.kind === "file" && isSymlinkEscape(target.path, input.cwd)) return target;
|
|
15205
|
+
if (targetFullyAllowed(target, cfg.allow, allowRes)) return null;
|
|
15206
|
+
return targetIntersectsPatterns(target, cfg.protect, protectRes) ? target : null;
|
|
15207
|
+
})
|
|
15208
|
+
);
|
|
15209
|
+
const firstProtectedWrite = writeHits.find((t) => t !== null);
|
|
15210
|
+
if (firstProtectedWrite) {
|
|
15211
|
+
return verdict(
|
|
15212
|
+
firstProtectedWrite.path,
|
|
15213
|
+
toolName,
|
|
15214
|
+
operationLabel(toolName),
|
|
15215
|
+
firstProtectedWrite.kind !== "file"
|
|
15216
|
+
);
|
|
14941
15217
|
}
|
|
14942
15218
|
return;
|
|
14943
15219
|
};
|
|
@@ -14964,7 +15240,8 @@ var plugin39 = {
|
|
|
14964
15240
|
counters: {
|
|
14965
15241
|
invocations: state60.invocations,
|
|
14966
15242
|
blocks: state60.blocks,
|
|
14967
|
-
warns: state60.warns
|
|
15243
|
+
warns: state60.warns,
|
|
15244
|
+
redosTimeouts: state60.redosTimeouts
|
|
14968
15245
|
},
|
|
14969
15246
|
lastBlock: state60.lastBlock
|
|
14970
15247
|
};
|
|
@@ -14987,10 +15264,16 @@ var plugin39 = {
|
|
|
14987
15264
|
}
|
|
14988
15265
|
state60.hookUnregister = null;
|
|
14989
15266
|
}
|
|
14990
|
-
const final = {
|
|
15267
|
+
const final = {
|
|
15268
|
+
invocations: state60.invocations,
|
|
15269
|
+
blocks: state60.blocks,
|
|
15270
|
+
warns: state60.warns,
|
|
15271
|
+
redosTimeouts: state60.redosTimeouts
|
|
15272
|
+
};
|
|
14991
15273
|
state60.invocations = 0;
|
|
14992
15274
|
state60.blocks = 0;
|
|
14993
15275
|
state60.warns = 0;
|
|
15276
|
+
state60.redosTimeouts = 0;
|
|
14994
15277
|
state60.lastBlock = null;
|
|
14995
15278
|
states.delete(api);
|
|
14996
15279
|
api.log.info("path-guard: teardown complete", { final });
|
|
@@ -15003,7 +15286,8 @@ var plugin39 = {
|
|
|
15003
15286
|
counters: {
|
|
15004
15287
|
invocations: state60.invocations,
|
|
15005
15288
|
blocks: state60.blocks,
|
|
15006
|
-
warns: state60.warns
|
|
15289
|
+
warns: state60.warns,
|
|
15290
|
+
redosTimeouts: state60.redosTimeouts
|
|
15007
15291
|
}
|
|
15008
15292
|
};
|
|
15009
15293
|
}
|
|
@@ -15012,7 +15296,7 @@ var path_guard_default = plugin39;
|
|
|
15012
15296
|
|
|
15013
15297
|
// src/performance-regression-gate/index.ts
|
|
15014
15298
|
import { existsSync as existsSync5, readFileSync as readFileSync11 } from "node:fs";
|
|
15015
|
-
import { isAbsolute as
|
|
15299
|
+
import { isAbsolute as isAbsolute18, relative as relative18, resolve as resolve19 } from "node:path";
|
|
15016
15300
|
var API_VERSION26 = "^0.1.10";
|
|
15017
15301
|
var state36 = {
|
|
15018
15302
|
invocationCount: 0,
|
|
@@ -15035,21 +15319,21 @@ function readConfig35(raw) {
|
|
|
15035
15319
|
thresholdPercent: threshold
|
|
15036
15320
|
};
|
|
15037
15321
|
}
|
|
15038
|
-
function
|
|
15322
|
+
function withinProject3(p, cwd = process.cwd()) {
|
|
15039
15323
|
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
15040
15324
|
const root = resolve19(cwd);
|
|
15041
|
-
const resolved =
|
|
15042
|
-
const rel =
|
|
15325
|
+
const resolved = isAbsolute18(p) ? resolve19(p) : resolve19(root, p);
|
|
15326
|
+
const rel = relative18(root, resolved);
|
|
15043
15327
|
if (rel === "" || rel === ".") return true;
|
|
15044
15328
|
if (rel.startsWith("..")) return false;
|
|
15045
|
-
if (
|
|
15329
|
+
if (isAbsolute18(rel)) return false;
|
|
15046
15330
|
return true;
|
|
15047
15331
|
}
|
|
15048
15332
|
function resolveProjectPath6(rawPath, cwd = process.cwd()) {
|
|
15049
15333
|
if (typeof rawPath !== "string" || rawPath.length === 0) return null;
|
|
15050
15334
|
const root = resolve19(cwd);
|
|
15051
|
-
const resolved =
|
|
15052
|
-
if (!
|
|
15335
|
+
const resolved = isAbsolute18(rawPath) ? resolve19(rawPath) : resolve19(root, rawPath);
|
|
15336
|
+
if (!withinProject3(resolved, cwd)) return null;
|
|
15053
15337
|
return resolved;
|
|
15054
15338
|
}
|
|
15055
15339
|
function isValidNumber(n) {
|
|
@@ -15463,7 +15747,7 @@ var plugin_stack_observer_default = PLUGIN;
|
|
|
15463
15747
|
// src/pr-drafter/index.ts
|
|
15464
15748
|
import { execFile as execFile10 } from "node:child_process";
|
|
15465
15749
|
import { mkdir as mkdir2, writeFile as writeFile4 } from "node:fs/promises";
|
|
15466
|
-
import { dirname as dirname7, isAbsolute as
|
|
15750
|
+
import { dirname as dirname7, isAbsolute as isAbsolute19, relative as relative19, resolve as resolve20 } from "node:path";
|
|
15467
15751
|
var API_VERSION27 = "^0.1.10";
|
|
15468
15752
|
var state38 = {
|
|
15469
15753
|
commits: [],
|
|
@@ -15503,9 +15787,9 @@ function readConfig37(raw) {
|
|
|
15503
15787
|
function resolveProjectPath7(rawPath, cwd = process.cwd()) {
|
|
15504
15788
|
if (typeof rawPath !== "string" || rawPath.length === 0) return null;
|
|
15505
15789
|
const root = resolve20(cwd);
|
|
15506
|
-
const resolved =
|
|
15507
|
-
const rel =
|
|
15508
|
-
if (rel === "" || !rel.startsWith("..") && !
|
|
15790
|
+
const resolved = isAbsolute19(rawPath) ? resolve20(rawPath) : resolve20(root, rawPath);
|
|
15791
|
+
const rel = relative19(root, resolved);
|
|
15792
|
+
if (rel === "" || !rel.startsWith("..") && !isAbsolute19(rel)) return resolved;
|
|
15509
15793
|
return null;
|
|
15510
15794
|
}
|
|
15511
15795
|
function runGit4(args, timeout) {
|
|
@@ -15949,6 +16233,9 @@ var plugin42 = {
|
|
|
15949
16233
|
};
|
|
15950
16234
|
var process_guard_default = plugin42;
|
|
15951
16235
|
|
|
16236
|
+
// src/prompt-firewall/index.ts
|
|
16237
|
+
import { performance as performance2 } from "node:perf_hooks";
|
|
16238
|
+
|
|
15952
16239
|
// src/runtime/credential-patterns.ts
|
|
15953
16240
|
var CREDENTIAL_PATTERNS = [
|
|
15954
16241
|
// LLM provider keys
|
|
@@ -16120,32 +16407,156 @@ var PATTERNS2 = [
|
|
|
16120
16407
|
})),
|
|
16121
16408
|
...EXTRA_PATTERNS
|
|
16122
16409
|
];
|
|
16123
|
-
|
|
16124
|
-
|
|
16125
|
-
|
|
16126
|
-
|
|
16127
|
-
|
|
16410
|
+
var PATTERN_BUDGET_MS = 250;
|
|
16411
|
+
var GUARD_PROBE_LENGTH = 1e5;
|
|
16412
|
+
var GUARD_PROBE_OVERLAP = 4096;
|
|
16413
|
+
var SCAN_PASS_BUDGET_MS = 250;
|
|
16414
|
+
function createScanDeadline() {
|
|
16415
|
+
return { deadline: performance2.now() + SCAN_PASS_BUDGET_MS, tripped: /* @__PURE__ */ new Set() };
|
|
16416
|
+
}
|
|
16417
|
+
var SCAN_WINDOW_STRIDE = GUARD_PROBE_LENGTH - GUARD_PROBE_OVERLAP;
|
|
16418
|
+
var SCAN_WINDOW_TAIL = 65536;
|
|
16419
|
+
var SCAN_WINDOW_LIMIT = GUARD_PROBE_LENGTH + SCAN_WINDOW_TAIL;
|
|
16420
|
+
var SCAN_MAX_GROWTH = 16;
|
|
16421
|
+
function growMatch(re, p, text, absStart, deadline) {
|
|
16422
|
+
let size = GUARD_PROBE_LENGTH;
|
|
16423
|
+
for (let grown = 0; grown < SCAN_MAX_GROWTH; grown++) {
|
|
16424
|
+
if (deadline && performance2.now() > deadline.deadline) {
|
|
16425
|
+
deadline.tripped.add(p.kind);
|
|
16426
|
+
return null;
|
|
16427
|
+
}
|
|
16428
|
+
const extEnd = Math.min(text.length, absStart + size);
|
|
16429
|
+
const ext = text.slice(absStart, extEnd);
|
|
16430
|
+
re.lastIndex = 0;
|
|
16431
|
+
const em = re.exec(ext);
|
|
16432
|
+
if (!em || em.index !== 0 || em[0].length === 0) return null;
|
|
16433
|
+
const end = absStart + em[0].length;
|
|
16434
|
+
if (end < extEnd || extEnd === text.length) {
|
|
16435
|
+
return { start: absStart, end, matched: em[0] };
|
|
16436
|
+
}
|
|
16437
|
+
size *= 2;
|
|
16438
|
+
}
|
|
16439
|
+
return null;
|
|
16440
|
+
}
|
|
16441
|
+
function* execWindowed(p, text, deadline) {
|
|
16442
|
+
const re = new RegExp(p.re.source, p.re.flags);
|
|
16443
|
+
let acceptLo = 0;
|
|
16444
|
+
let highWater = 0;
|
|
16445
|
+
for (let window = 0; acceptLo < text.length; window++) {
|
|
16446
|
+
const sliceStart = window === 0 ? 0 : acceptLo - GUARD_PROBE_OVERLAP;
|
|
16447
|
+
const sliceEnd = Math.min(text.length, sliceStart + SCAN_WINDOW_LIMIT);
|
|
16448
|
+
const slice = text.slice(sliceStart, sliceEnd);
|
|
16449
|
+
const acceptHi = Math.min(acceptLo + SCAN_WINDOW_STRIDE, text.length);
|
|
16450
|
+
re.lastIndex = 0;
|
|
16451
|
+
let m = re.exec(slice);
|
|
16128
16452
|
while (m !== null) {
|
|
16453
|
+
if (deadline && performance2.now() > deadline.deadline) {
|
|
16454
|
+
deadline.tripped.add(p.kind);
|
|
16455
|
+
return;
|
|
16456
|
+
}
|
|
16129
16457
|
const matched = m[0];
|
|
16130
|
-
if (
|
|
16131
|
-
|
|
16458
|
+
if (matched.length === 0) {
|
|
16459
|
+
re.lastIndex += 1;
|
|
16460
|
+
m = re.exec(slice);
|
|
16461
|
+
continue;
|
|
16462
|
+
}
|
|
16463
|
+
const absStart = sliceStart + m.index;
|
|
16464
|
+
const absEnd = absStart + matched.length;
|
|
16465
|
+
if (absStart >= acceptLo && absStart < acceptHi) {
|
|
16466
|
+
let final = { start: absStart, end: absEnd, matched };
|
|
16467
|
+
if (absEnd === sliceEnd && sliceEnd < text.length) {
|
|
16468
|
+
final = growMatch(re, p, text, absStart, deadline) ?? final;
|
|
16469
|
+
re.lastIndex = m.index + final.matched.length;
|
|
16470
|
+
}
|
|
16471
|
+
if (final.start >= highWater) {
|
|
16472
|
+
highWater = Math.max(highWater, final.end);
|
|
16473
|
+
yield final;
|
|
16474
|
+
}
|
|
16132
16475
|
}
|
|
16133
|
-
m =
|
|
16476
|
+
m = re.exec(slice);
|
|
16477
|
+
}
|
|
16478
|
+
acceptLo += SCAN_WINDOW_STRIDE;
|
|
16479
|
+
}
|
|
16480
|
+
}
|
|
16481
|
+
function countMatches(p, text, allow, counts, deadline) {
|
|
16482
|
+
if (deadline && performance2.now() > deadline.deadline) {
|
|
16483
|
+
deadline.tripped.add(p.kind);
|
|
16484
|
+
return;
|
|
16485
|
+
}
|
|
16486
|
+
for (const m of execWindowed(p, text, deadline)) {
|
|
16487
|
+
if (!allow.some((a) => a.test(m.matched))) {
|
|
16488
|
+
counts.set(p.kind, (counts.get(p.kind) ?? 0) + 1);
|
|
16489
|
+
}
|
|
16490
|
+
}
|
|
16491
|
+
}
|
|
16492
|
+
function replacePattern(p, text, allow, redactions, deadline) {
|
|
16493
|
+
if (deadline && performance2.now() > deadline.deadline) {
|
|
16494
|
+
deadline.tripped.add(p.kind);
|
|
16495
|
+
return text;
|
|
16496
|
+
}
|
|
16497
|
+
let out = "";
|
|
16498
|
+
let copied = 0;
|
|
16499
|
+
for (const m of execWindowed(p, text, deadline)) {
|
|
16500
|
+
out += text.slice(copied, m.start);
|
|
16501
|
+
if (allow.some((a) => a.test(m.matched))) {
|
|
16502
|
+
out += m.matched;
|
|
16503
|
+
} else {
|
|
16504
|
+
redactions.n += 1;
|
|
16505
|
+
out += `[REDACTED:${p.kind}]`;
|
|
16134
16506
|
}
|
|
16507
|
+
copied = m.end;
|
|
16135
16508
|
}
|
|
16136
|
-
return
|
|
16509
|
+
return out + text.slice(copied);
|
|
16137
16510
|
}
|
|
16138
|
-
function redactSecrets(text, allow) {
|
|
16511
|
+
function redactSecrets(text, allow, deadline) {
|
|
16512
|
+
const redactions = { n: 0 };
|
|
16139
16513
|
let out = text;
|
|
16140
|
-
|
|
16141
|
-
|
|
16142
|
-
|
|
16143
|
-
|
|
16144
|
-
|
|
16145
|
-
|
|
16514
|
+
for (const p of PATTERNS2) out = replacePattern(p, out, allow, redactions, deadline);
|
|
16515
|
+
return { text: out, redactions: redactions.n };
|
|
16516
|
+
}
|
|
16517
|
+
var DISTINCT_PATTERN_KINDS = new Set(PATTERNS2.map((p) => p.kind)).size;
|
|
16518
|
+
async function probeTimedOutPatterns(text) {
|
|
16519
|
+
const timedOut = /* @__PURE__ */ new Set();
|
|
16520
|
+
if (text.length === 0) return timedOut;
|
|
16521
|
+
const probeWindow = (offset) => {
|
|
16522
|
+
const window = text.slice(offset, offset + GUARD_PROBE_LENGTH);
|
|
16523
|
+
const combined = new RegExp(PATTERNS2.map((p) => `(${p.re.source})`).join("|"), "gi");
|
|
16524
|
+
return withReDoSGuard(combined, window, PATTERN_BUDGET_MS).then(async (combinedResult) => {
|
|
16525
|
+
if (!combinedResult.timedOut) return;
|
|
16526
|
+
for (const p of PATTERNS2) {
|
|
16527
|
+
if (timedOut.has(p.kind)) continue;
|
|
16528
|
+
const result = await withReDoSGuard(p.re, window, PATTERN_BUDGET_MS);
|
|
16529
|
+
if (result.timedOut) timedOut.add(p.kind);
|
|
16530
|
+
}
|
|
16146
16531
|
});
|
|
16532
|
+
};
|
|
16533
|
+
const stride = GUARD_PROBE_LENGTH - GUARD_PROBE_OVERLAP;
|
|
16534
|
+
for (let offset = 0; offset < text.length; offset += stride) {
|
|
16535
|
+
if (timedOut.size >= DISTINCT_PATTERN_KINDS) break;
|
|
16536
|
+
await probeWindow(offset);
|
|
16147
16537
|
}
|
|
16148
|
-
return
|
|
16538
|
+
return timedOut;
|
|
16539
|
+
}
|
|
16540
|
+
async function detectSecretsGuarded(text, allow) {
|
|
16541
|
+
const timedOut = await probeTimedOutPatterns(text);
|
|
16542
|
+
const deadline = createScanDeadline();
|
|
16543
|
+
const counts = /* @__PURE__ */ new Map();
|
|
16544
|
+
for (const p of PATTERNS2) {
|
|
16545
|
+
if (!timedOut.has(p.kind)) countMatches(p, text, allow, counts, deadline);
|
|
16546
|
+
}
|
|
16547
|
+
const allSkipped = /* @__PURE__ */ new Set([...timedOut, ...deadline.tripped]);
|
|
16548
|
+
return {
|
|
16549
|
+
detections: [...counts.entries()].map(([kind, count]) => ({ kind, count })),
|
|
16550
|
+
skipped: [...allSkipped].sort().map((kind) => ({ kind, reason: "redos-timeout" }))
|
|
16551
|
+
};
|
|
16552
|
+
}
|
|
16553
|
+
function redactSecretsGuarded(text, allow, skip, deadline) {
|
|
16554
|
+
const redactions = { n: 0 };
|
|
16555
|
+
let out = text;
|
|
16556
|
+
for (const p of PATTERNS2) {
|
|
16557
|
+
if (!skip.has(p.kind)) out = replacePattern(p, out, allow, redactions, deadline);
|
|
16558
|
+
}
|
|
16559
|
+
return { text: out, redactions: redactions.n };
|
|
16149
16560
|
}
|
|
16150
16561
|
function collectText(request) {
|
|
16151
16562
|
const parts = [];
|
|
@@ -16159,17 +16570,27 @@ function collectText(request) {
|
|
|
16159
16570
|
walk(request["messages"]);
|
|
16160
16571
|
return parts.join("\n");
|
|
16161
16572
|
}
|
|
16162
|
-
|
|
16573
|
+
var RESPONSE_SCAN_BUDGET = 1e6;
|
|
16574
|
+
function redactDeep(value, allow, counter, skip, budget, deadline) {
|
|
16163
16575
|
if (typeof value === "string") {
|
|
16164
|
-
|
|
16576
|
+
if (budget) {
|
|
16577
|
+
if (value.length > budget.remaining) {
|
|
16578
|
+
budget.remaining = 0;
|
|
16579
|
+
budget.truncated = true;
|
|
16580
|
+
return value;
|
|
16581
|
+
}
|
|
16582
|
+
budget.remaining -= value.length;
|
|
16583
|
+
}
|
|
16584
|
+
const { text, redactions } = skip ? redactSecretsGuarded(value, allow, skip, deadline) : redactSecrets(value, allow, deadline);
|
|
16165
16585
|
counter.n += redactions;
|
|
16166
16586
|
return text;
|
|
16167
16587
|
}
|
|
16168
|
-
if (Array.isArray(value))
|
|
16588
|
+
if (Array.isArray(value))
|
|
16589
|
+
return value.map((v) => redactDeep(v, allow, counter, skip, budget, deadline));
|
|
16169
16590
|
if (value && typeof value === "object") {
|
|
16170
16591
|
const out = {};
|
|
16171
16592
|
for (const [k, v] of Object.entries(value)) {
|
|
16172
|
-
out[k] = redactDeep(v, allow, counter);
|
|
16593
|
+
out[k] = redactDeep(v, allow, counter, skip, budget, deadline);
|
|
16173
16594
|
}
|
|
16174
16595
|
return out;
|
|
16175
16596
|
}
|
|
@@ -16204,16 +16625,34 @@ var state40 = {
|
|
|
16204
16625
|
requestRedactions: 0,
|
|
16205
16626
|
responseRedactions: 0,
|
|
16206
16627
|
blocked: 0,
|
|
16628
|
+
timeoutCount: 0,
|
|
16629
|
+
skippedPatterns: [],
|
|
16630
|
+
responseTruncated: false,
|
|
16207
16631
|
byKind: /* @__PURE__ */ new Map(),
|
|
16208
16632
|
lastDetection: null,
|
|
16209
16633
|
extensionUnregister: null
|
|
16210
16634
|
};
|
|
16635
|
+
function surfaceScanTrips(api, tripped) {
|
|
16636
|
+
for (const kind of tripped) {
|
|
16637
|
+
if (!state40.skippedPatterns.includes(kind)) state40.skippedPatterns.push(kind);
|
|
16638
|
+
}
|
|
16639
|
+
state40.timeoutCount += 1;
|
|
16640
|
+
api.metrics.counter("redos_skips", 1);
|
|
16641
|
+
api.log.warn("prompt-firewall: scan-pass budget exceeded \u2014 patterns skipped mid-pass (issue #370)", {
|
|
16642
|
+
skipped: [...tripped]
|
|
16643
|
+
});
|
|
16644
|
+
}
|
|
16211
16645
|
var plugin43 = {
|
|
16212
16646
|
name: "prompt-firewall",
|
|
16213
16647
|
version: "0.1.0",
|
|
16214
16648
|
description: "Scans the provider wire for credential leaks before context reaches the LLM API (wrapProviderRunner); redact/warn/block. Opt-in; redact by default.",
|
|
16215
16649
|
apiVersion: "^0.1.10",
|
|
16216
16650
|
capabilities: { tools: true },
|
|
16651
|
+
// Wrap-stack contract (issue #362): ExtensionRegistry composes wrappers
|
|
16652
|
+
// first-registered = outermost. The manifest lists this plugin before
|
|
16653
|
+
// llm-cache, and llm-cache declares this plugin in optionalDeps, so the
|
|
16654
|
+
// firewall is the outer wrap: every request is scanned/redacted before
|
|
16655
|
+
// llm-cache can fingerprint or cache it.
|
|
16217
16656
|
defaultConfig: { enabled: false, mode: "redact", scanResponse: true, allow: [] },
|
|
16218
16657
|
configSchema: {
|
|
16219
16658
|
type: "object",
|
|
@@ -16248,6 +16687,9 @@ var plugin43 = {
|
|
|
16248
16687
|
state40.requestRedactions = 0;
|
|
16249
16688
|
state40.responseRedactions = 0;
|
|
16250
16689
|
state40.blocked = 0;
|
|
16690
|
+
state40.timeoutCount = 0;
|
|
16691
|
+
state40.skippedPatterns = [];
|
|
16692
|
+
state40.responseTruncated = false;
|
|
16251
16693
|
state40.byKind.clear();
|
|
16252
16694
|
state40.lastDetection = null;
|
|
16253
16695
|
if (state40.extensionUnregister) {
|
|
@@ -16270,7 +16712,17 @@ var plugin43 = {
|
|
|
16270
16712
|
async wrapProviderRunner(_ctx, request, inner) {
|
|
16271
16713
|
const req = request ?? {};
|
|
16272
16714
|
state40.invocations += 1;
|
|
16273
|
-
const
|
|
16715
|
+
const requestText = collectText(req);
|
|
16716
|
+
const { detections, skipped } = await detectSecretsGuarded(requestText, cfg.allow);
|
|
16717
|
+
state40.skippedPatterns = skipped.map((s) => s.kind);
|
|
16718
|
+
if (skipped.length > 0) {
|
|
16719
|
+
state40.timeoutCount += 1;
|
|
16720
|
+
api.log.warn("prompt-firewall: ReDoS budget exceeded, patterns skipped", {
|
|
16721
|
+
skipped: state40.skippedPatterns
|
|
16722
|
+
});
|
|
16723
|
+
api.metrics.counter("redos_skips", 1);
|
|
16724
|
+
}
|
|
16725
|
+
const skipSet = new Set(state40.skippedPatterns);
|
|
16274
16726
|
if (detections.length > 0) {
|
|
16275
16727
|
state40.requestsWithSecrets += 1;
|
|
16276
16728
|
for (const d of detections) {
|
|
@@ -16289,27 +16741,39 @@ var plugin43 = {
|
|
|
16289
16741
|
}
|
|
16290
16742
|
if (cfg.mode === "redact") {
|
|
16291
16743
|
const counter = { n: 0 };
|
|
16292
|
-
const
|
|
16744
|
+
const deadline = createScanDeadline();
|
|
16745
|
+
const redactedReq = redactDeep(req, cfg.allow, counter, skipSet, void 0, deadline);
|
|
16293
16746
|
state40.requestRedactions += counter.n;
|
|
16294
16747
|
api.metrics.counter("request_redactions", counter.n);
|
|
16748
|
+
if (deadline.tripped.size > 0) surfaceScanTrips(api, deadline.tripped);
|
|
16295
16749
|
const response2 = await inner(_ctx, redactedReq);
|
|
16296
|
-
return cfg.scanResponse ? redactResponse(response2, cfg.allow) : response2;
|
|
16750
|
+
return cfg.scanResponse ? redactResponse(response2, cfg.allow, skipSet) : response2;
|
|
16297
16751
|
}
|
|
16298
16752
|
}
|
|
16299
16753
|
const response = await inner(_ctx, request);
|
|
16300
16754
|
if (cfg.mode === "redact" && cfg.scanResponse) {
|
|
16301
|
-
return redactResponse(response, cfg.allow);
|
|
16755
|
+
return redactResponse(response, cfg.allow, skipSet);
|
|
16302
16756
|
}
|
|
16303
16757
|
return response;
|
|
16304
16758
|
}
|
|
16305
16759
|
});
|
|
16306
16760
|
}
|
|
16307
|
-
function redactResponse(response, allow) {
|
|
16761
|
+
function redactResponse(response, allow, skip) {
|
|
16308
16762
|
if (!response || typeof response !== "object") return response;
|
|
16309
16763
|
const counter = { n: 0 };
|
|
16310
16764
|
const content = response.content;
|
|
16311
16765
|
if (content === void 0) return response;
|
|
16312
|
-
const
|
|
16766
|
+
const budget = { remaining: RESPONSE_SCAN_BUDGET, truncated: false };
|
|
16767
|
+
const deadline = createScanDeadline();
|
|
16768
|
+
const redacted = redactDeep(content, allow, counter, skip, budget, deadline);
|
|
16769
|
+
if (deadline.tripped.size > 0) surfaceScanTrips(api, deadline.tripped);
|
|
16770
|
+
if (budget.truncated) {
|
|
16771
|
+
state40.responseTruncated = true;
|
|
16772
|
+
api.log.warn(
|
|
16773
|
+
"prompt-firewall: response scan budget exhausted \u2014 part of the response was returned unredacted"
|
|
16774
|
+
);
|
|
16775
|
+
api.metrics.counter("response_scan_truncated", 1);
|
|
16776
|
+
}
|
|
16313
16777
|
if (counter.n > 0) {
|
|
16314
16778
|
state40.responseRedactions += counter.n;
|
|
16315
16779
|
api.metrics.counter("response_redactions", counter.n);
|
|
@@ -16335,12 +16799,15 @@ var plugin43 = {
|
|
|
16335
16799
|
mode: cfg.mode,
|
|
16336
16800
|
scanResponse: cfg.scanResponse,
|
|
16337
16801
|
patterns: PATTERNS2.map((p) => p.kind),
|
|
16802
|
+
skippedPatterns: state40.skippedPatterns,
|
|
16803
|
+
responseTruncated: state40.responseTruncated,
|
|
16338
16804
|
counters: {
|
|
16339
16805
|
invocations: state40.invocations,
|
|
16340
16806
|
requestsWithSecrets: state40.requestsWithSecrets,
|
|
16341
16807
|
requestRedactions: state40.requestRedactions,
|
|
16342
16808
|
responseRedactions: state40.responseRedactions,
|
|
16343
|
-
blocked: state40.blocked
|
|
16809
|
+
blocked: state40.blocked,
|
|
16810
|
+
timeoutCount: state40.timeoutCount
|
|
16344
16811
|
},
|
|
16345
16812
|
byKind: Object.fromEntries(state40.byKind),
|
|
16346
16813
|
lastDetection: state40.lastDetection
|
|
@@ -16367,13 +16834,17 @@ var plugin43 = {
|
|
|
16367
16834
|
requestsWithSecrets: state40.requestsWithSecrets,
|
|
16368
16835
|
requestRedactions: state40.requestRedactions,
|
|
16369
16836
|
responseRedactions: state40.responseRedactions,
|
|
16370
|
-
blocked: state40.blocked
|
|
16837
|
+
blocked: state40.blocked,
|
|
16838
|
+
timeoutCount: state40.timeoutCount
|
|
16371
16839
|
};
|
|
16372
16840
|
state40.invocations = 0;
|
|
16373
16841
|
state40.requestsWithSecrets = 0;
|
|
16374
16842
|
state40.requestRedactions = 0;
|
|
16375
16843
|
state40.responseRedactions = 0;
|
|
16376
16844
|
state40.blocked = 0;
|
|
16845
|
+
state40.timeoutCount = 0;
|
|
16846
|
+
state40.skippedPatterns = [];
|
|
16847
|
+
state40.responseTruncated = false;
|
|
16377
16848
|
state40.byKind.clear();
|
|
16378
16849
|
state40.lastDetection = null;
|
|
16379
16850
|
api.log.info("prompt-firewall: teardown complete", { final });
|
|
@@ -16396,7 +16867,7 @@ var prompt_firewall_default = plugin43;
|
|
|
16396
16867
|
|
|
16397
16868
|
// src/refactor-suggester/index.ts
|
|
16398
16869
|
import { readFile as readFile12 } from "node:fs/promises";
|
|
16399
|
-
import { isAbsolute as
|
|
16870
|
+
import { isAbsolute as isAbsolute20, relative as relative20, resolve as resolve21 } from "node:path";
|
|
16400
16871
|
var API_VERSION28 = "^0.1.10";
|
|
16401
16872
|
var HOOK_WARNING_COOLDOWN_MS2 = 6e4;
|
|
16402
16873
|
var state41 = {
|
|
@@ -16440,7 +16911,7 @@ function toPosix6(p) {
|
|
|
16440
16911
|
return p.replace(/\\/g, "/");
|
|
16441
16912
|
}
|
|
16442
16913
|
function relativePath6(p) {
|
|
16443
|
-
return toPosix6(
|
|
16914
|
+
return toPosix6(relative20(process.cwd(), p));
|
|
16444
16915
|
}
|
|
16445
16916
|
function leadingIndentLevel(line) {
|
|
16446
16917
|
const leading = line.match(/^(\s*)/)?.[1] ?? "";
|
|
@@ -16527,7 +16998,7 @@ function detectSmells(filePath, content, rules) {
|
|
|
16527
16998
|
}
|
|
16528
16999
|
async function scanPath4(rawPath, cfg) {
|
|
16529
17000
|
const root = process.cwd();
|
|
16530
|
-
const resolved =
|
|
17001
|
+
const resolved = isAbsolute20(rawPath) ? resolve21(rawPath) : resolve21(root, rawPath);
|
|
16531
17002
|
const exts = normalizeExtensions5(cfg.extensions);
|
|
16532
17003
|
const files = await collectSourceFilesAsync(resolved, { extensions: exts });
|
|
16533
17004
|
const suggestions = [];
|
|
@@ -17423,6 +17894,7 @@ function createState2() {
|
|
|
17423
17894
|
allowCount: 0,
|
|
17424
17895
|
/** PostToolUse: secrets detected in tool output. */
|
|
17425
17896
|
leakCount: 0,
|
|
17897
|
+
timeoutCount: 0,
|
|
17426
17898
|
/** Most recent PreToolUse block — surfaced by `secret_scanner_status`. */
|
|
17427
17899
|
lastBlock: null,
|
|
17428
17900
|
/** Most recent PostToolUse leak — surfaced by `secret_scanner_status`. */
|
|
@@ -17591,7 +18063,19 @@ function buildHook(cfg, log, runtime) {
|
|
|
17591
18063
|
const { state: state60 } = runtime;
|
|
17592
18064
|
if (!cfg.enabled) return;
|
|
17593
18065
|
const toolName = input.toolName ?? "unknown";
|
|
17594
|
-
|
|
18066
|
+
let matched;
|
|
18067
|
+
try {
|
|
18068
|
+
matched = scanInput(input.toolInput);
|
|
18069
|
+
} catch (err) {
|
|
18070
|
+
if (String(err).includes("ReDoS")) {
|
|
18071
|
+
state60.timeoutCount += 1;
|
|
18072
|
+
return {
|
|
18073
|
+
decision: "block",
|
|
18074
|
+
reason: "secret-scanner: ReDoS timeout \u2014 regex scan exceeded the wall-clock budget. Fail-closed: treated as a block."
|
|
18075
|
+
};
|
|
18076
|
+
}
|
|
18077
|
+
throw err;
|
|
18078
|
+
}
|
|
17595
18079
|
if (!matched) return;
|
|
17596
18080
|
const summary = matched.join(", ");
|
|
17597
18081
|
const when = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -17769,7 +18253,8 @@ var plugin47 = {
|
|
|
17769
18253
|
block: state60.blockCount,
|
|
17770
18254
|
redact: state60.redactCount,
|
|
17771
18255
|
allow: state60.allowCount,
|
|
17772
|
-
leak: state60.leakCount
|
|
18256
|
+
leak: state60.leakCount,
|
|
18257
|
+
timeoutCount: state60.timeoutCount
|
|
17773
18258
|
},
|
|
17774
18259
|
lastBlock: state60.lastBlock,
|
|
17775
18260
|
lastLeak: state60.lastLeak
|
|
@@ -17859,7 +18344,7 @@ var secret_scanner_default = plugin47;
|
|
|
17859
18344
|
|
|
17860
18345
|
// src/security-hotspot-scanner/index.ts
|
|
17861
18346
|
import { readdir as readdir2, readFile as readFile13, stat as stat5 } from "node:fs/promises";
|
|
17862
|
-
import { isAbsolute as
|
|
18347
|
+
import { isAbsolute as isAbsolute21, relative as relative21, resolve as resolve22 } from "node:path";
|
|
17863
18348
|
var API_VERSION31 = "^0.1.10";
|
|
17864
18349
|
var state44 = {
|
|
17865
18350
|
scanCount: 0,
|
|
@@ -17950,7 +18435,7 @@ function isSourceFile2(filePath, extensions) {
|
|
|
17950
18435
|
async function scanPath5(inputPath, cfg) {
|
|
17951
18436
|
const start = Date.now();
|
|
17952
18437
|
const root = process.cwd();
|
|
17953
|
-
const resolved =
|
|
18438
|
+
const resolved = isAbsolute21(inputPath) ? resolve22(inputPath) : resolve22(root, inputPath);
|
|
17954
18439
|
if (!withinProject(inputPath)) {
|
|
17955
18440
|
return {
|
|
17956
18441
|
path: inputPath,
|
|
@@ -17971,7 +18456,7 @@ async function scanPath5(inputPath, cfg) {
|
|
|
17971
18456
|
filesScanned += 1;
|
|
17972
18457
|
const findings = scanSource(content, maxPerFile);
|
|
17973
18458
|
for (const f of findings) {
|
|
17974
|
-
allFindings.push({ ...f, snippet: `${
|
|
18459
|
+
allFindings.push({ ...f, snippet: `${relative21(root, filePath)}:${f.line}: ${f.snippet}` });
|
|
17975
18460
|
if (allFindings.length >= cfg.maxFindings) return;
|
|
17976
18461
|
}
|
|
17977
18462
|
} catch {
|
|
@@ -18255,7 +18740,7 @@ var security_hotspot_scanner_default = plugin48;
|
|
|
18255
18740
|
|
|
18256
18741
|
// src/semantic-search-indexer/index.ts
|
|
18257
18742
|
import * as fs2 from "node:fs/promises";
|
|
18258
|
-
import { isAbsolute as
|
|
18743
|
+
import { isAbsolute as isAbsolute22, relative as relative22, resolve as resolve23 } from "node:path";
|
|
18259
18744
|
import { DEFAULT_WALK_IGNORE_DIRS } from "@wrongstack/core/utils";
|
|
18260
18745
|
var API_VERSION32 = "^0.1.10";
|
|
18261
18746
|
var escapeRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -18333,21 +18818,21 @@ function readConfig45(raw) {
|
|
|
18333
18818
|
function normalizeSlashes2(p) {
|
|
18334
18819
|
return p.replace(/\\/g, "/");
|
|
18335
18820
|
}
|
|
18336
|
-
function
|
|
18821
|
+
function withinProject4(p) {
|
|
18337
18822
|
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
18338
18823
|
const root = normalizeSlashes2(process.cwd());
|
|
18339
|
-
const resolved = normalizeSlashes2(
|
|
18340
|
-
const rel = normalizeSlashes2(
|
|
18824
|
+
const resolved = normalizeSlashes2(isAbsolute22(p) ? resolve23(p) : resolve23(root, p));
|
|
18825
|
+
const rel = normalizeSlashes2(relative22(root, resolved));
|
|
18341
18826
|
if (rel === "" || rel === ".") return true;
|
|
18342
18827
|
if (rel.startsWith("..")) return false;
|
|
18343
|
-
if (
|
|
18828
|
+
if (isAbsolute22(rel)) return false;
|
|
18344
18829
|
return true;
|
|
18345
18830
|
}
|
|
18346
18831
|
function resolveProjectPath8(p) {
|
|
18347
18832
|
const raw = typeof p === "string" && p.length > 0 ? p : ".";
|
|
18348
|
-
if (!
|
|
18833
|
+
if (!withinProject4(raw)) return null;
|
|
18349
18834
|
const root = normalizeSlashes2(process.cwd());
|
|
18350
|
-
return normalizeSlashes2(
|
|
18835
|
+
return normalizeSlashes2(isAbsolute22(raw) ? resolve23(raw) : resolve23(root, raw));
|
|
18351
18836
|
}
|
|
18352
18837
|
function tokenize(text, minLength) {
|
|
18353
18838
|
const tokens = [];
|
|
@@ -18432,7 +18917,7 @@ async function walkDirectory(absPath, cfg, excludes, fileBatch) {
|
|
|
18432
18917
|
return;
|
|
18433
18918
|
}
|
|
18434
18919
|
const absChild = normalizeSlashes2(resolve23(absPath, ent.name));
|
|
18435
|
-
const relChild = normalizeSlashes2(
|
|
18920
|
+
const relChild = normalizeSlashes2(relative22(root, absChild));
|
|
18436
18921
|
if (relChild === "" || relChild === ".") continue;
|
|
18437
18922
|
if (excludes.some((re) => re.test(relChild))) continue;
|
|
18438
18923
|
if (ent.isDirectory()) {
|
|
@@ -18474,7 +18959,7 @@ async function buildIndex(rootPath, cfg) {
|
|
|
18474
18959
|
return;
|
|
18475
18960
|
}
|
|
18476
18961
|
if (rootStats.isFile()) {
|
|
18477
|
-
const relPath = normalizeSlashes2(
|
|
18962
|
+
const relPath = normalizeSlashes2(relative22(normalizeSlashes2(process.cwd()), rootPath));
|
|
18478
18963
|
await indexFileFromStats(rootPath, relPath === "" ? "." : relPath, rootStats, cfg);
|
|
18479
18964
|
state45.fileCount = state45.index.files.size;
|
|
18480
18965
|
} else if (rootStats.isDirectory()) {
|
|
@@ -18771,14 +19256,14 @@ import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
|
18771
19256
|
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
18772
19257
|
import { execFile as execFile12 } from "node:child_process";
|
|
18773
19258
|
import { access as access4, readFile as readFile15, readdir as readdir4, writeFile as writeFile5 } from "node:fs/promises";
|
|
18774
|
-
import { isAbsolute as
|
|
19259
|
+
import { isAbsolute as isAbsolute23, join as join6, relative as relative23, resolve as resolve24 } from "node:path";
|
|
18775
19260
|
var API_VERSION33 = "^0.1.10";
|
|
18776
19261
|
function resolveProjectRoot(rawCwd, root = process.cwd()) {
|
|
18777
19262
|
if (typeof rawCwd !== "string" || rawCwd.length === 0) return root;
|
|
18778
19263
|
const base = resolve24(root);
|
|
18779
|
-
const resolved =
|
|
18780
|
-
const rel =
|
|
18781
|
-
if (rel === "" || !rel.startsWith("..") && !
|
|
19264
|
+
const resolved = isAbsolute23(rawCwd) ? resolve24(rawCwd) : resolve24(base, rawCwd);
|
|
19265
|
+
const rel = relative23(base, resolved);
|
|
19266
|
+
if (rel === "" || !rel.startsWith("..") && !isAbsolute23(rel)) return resolved;
|
|
18782
19267
|
return null;
|
|
18783
19268
|
}
|
|
18784
19269
|
var state46 = {
|
|
@@ -19801,16 +20286,16 @@ var session_recap_default = plugin51;
|
|
|
19801
20286
|
// src/shell-check/index.ts
|
|
19802
20287
|
import { execFile as execFile13 } from "node:child_process";
|
|
19803
20288
|
import { readdir as readdir5 } from "node:fs/promises";
|
|
19804
|
-
import { isAbsolute as
|
|
20289
|
+
import { isAbsolute as isAbsolute24, join as join7, relative as relative24, resolve as resolve25 } from "node:path";
|
|
19805
20290
|
var API_VERSION34 = "^0.1.10";
|
|
19806
|
-
function
|
|
20291
|
+
function withinProject5(p) {
|
|
19807
20292
|
if (p.startsWith("-")) return false;
|
|
19808
20293
|
const root = process.cwd();
|
|
19809
|
-
const resolved =
|
|
19810
|
-
const rel =
|
|
20294
|
+
const resolved = isAbsolute24(p) ? resolve25(p) : resolve25(root, p);
|
|
20295
|
+
const rel = relative24(root, resolved);
|
|
19811
20296
|
if (rel === "" || rel === ".") return true;
|
|
19812
20297
|
if (rel.startsWith("..")) return false;
|
|
19813
|
-
if (
|
|
20298
|
+
if (isAbsolute24(rel)) return false;
|
|
19814
20299
|
return true;
|
|
19815
20300
|
}
|
|
19816
20301
|
var MAX_PATH_LEN = 4096;
|
|
@@ -19987,7 +20472,7 @@ var plugin52 = {
|
|
|
19987
20472
|
const pattern = inp.pattern ?? "";
|
|
19988
20473
|
const severity = inp.severity ?? "warning";
|
|
19989
20474
|
state48.invocationCount += 1;
|
|
19990
|
-
const pathIsSafe = (p) => typeof p === "string" && p.length > 0 && p.length <= MAX_PATH_LEN &&
|
|
20475
|
+
const pathIsSafe = (p) => typeof p === "string" && p.length > 0 && p.length <= MAX_PATH_LEN && withinProject5(p);
|
|
19991
20476
|
if (!pathIsSafe(directory)) {
|
|
19992
20477
|
return {
|
|
19993
20478
|
ok: false,
|
|
@@ -20103,7 +20588,7 @@ var shell_check_default = plugin52;
|
|
|
20103
20588
|
|
|
20104
20589
|
// src/smart-rename/index.ts
|
|
20105
20590
|
import { readFileSync as readFileSync13, writeFileSync as writeFileSync2 } from "node:fs";
|
|
20106
|
-
import { extname as extname6, isAbsolute as
|
|
20591
|
+
import { extname as extname6, isAbsolute as isAbsolute25, relative as relative25, resolve as resolve26 } from "node:path";
|
|
20107
20592
|
var API_VERSION35 = "^0.1.10";
|
|
20108
20593
|
var state49 = {
|
|
20109
20594
|
renameCount: 0,
|
|
@@ -20122,21 +20607,21 @@ function readConfig47(raw) {
|
|
|
20122
20607
|
extensions: Array.isArray(r["extensions"]) ? r["extensions"].filter((x) => typeof x === "string") : DEFAULTS44.extensions
|
|
20123
20608
|
};
|
|
20124
20609
|
}
|
|
20125
|
-
function
|
|
20610
|
+
function withinProject6(p) {
|
|
20126
20611
|
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
20127
20612
|
const root = process.cwd();
|
|
20128
|
-
const resolved =
|
|
20129
|
-
const rel =
|
|
20613
|
+
const resolved = isAbsolute25(p) ? resolve26(p) : resolve26(root, p);
|
|
20614
|
+
const rel = relative25(root, resolved);
|
|
20130
20615
|
if (rel === "" || rel === ".") return true;
|
|
20131
20616
|
if (rel.startsWith("..")) return false;
|
|
20132
|
-
if (
|
|
20617
|
+
if (isAbsolute25(rel)) return false;
|
|
20133
20618
|
return true;
|
|
20134
20619
|
}
|
|
20135
20620
|
function toPosix7(p) {
|
|
20136
20621
|
return p.replace(/\\/g, "/");
|
|
20137
20622
|
}
|
|
20138
20623
|
function relativePath7(p) {
|
|
20139
|
-
return toPosix7(
|
|
20624
|
+
return toPosix7(relative25(process.cwd(), p));
|
|
20140
20625
|
}
|
|
20141
20626
|
function escapeRegex2(s) {
|
|
20142
20627
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -20221,7 +20706,7 @@ var plugin53 = {
|
|
|
20221
20706
|
if (!isIdentifier(newName)) {
|
|
20222
20707
|
return { ok: false, error: `newName "${newName}" is not a valid identifier` };
|
|
20223
20708
|
}
|
|
20224
|
-
if (!
|
|
20709
|
+
if (!withinProject6(rawPath)) {
|
|
20225
20710
|
return { ok: false, error: "path is outside the project root" };
|
|
20226
20711
|
}
|
|
20227
20712
|
const ext = extname6(rawPath).toLowerCase();
|
|
@@ -20326,10 +20811,10 @@ var OFFICIAL_PLUGIN_NAMES = [
|
|
|
20326
20811
|
"notify-hub",
|
|
20327
20812
|
"changelog-writer",
|
|
20328
20813
|
"injection-shield",
|
|
20814
|
+
"prompt-firewall",
|
|
20329
20815
|
"llm-cache",
|
|
20330
20816
|
"model-router",
|
|
20331
20817
|
"pr-drafter",
|
|
20332
|
-
"prompt-firewall",
|
|
20333
20818
|
"auto-escalate",
|
|
20334
20819
|
"test-coverage-gate",
|
|
20335
20820
|
"type-gate",
|
|
@@ -20499,7 +20984,7 @@ function isWrappedAsLinkOrCode(line, name) {
|
|
|
20499
20984
|
if (lower.includes(`[\``) && lower.includes(`\`](`)) return true;
|
|
20500
20985
|
return false;
|
|
20501
20986
|
}
|
|
20502
|
-
function
|
|
20987
|
+
function escapeRegExp2(s) {
|
|
20503
20988
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20504
20989
|
}
|
|
20505
20990
|
function mapMarkdownFences(lines) {
|
|
@@ -20524,7 +21009,7 @@ function findUnlinkedReferences(lines, names) {
|
|
|
20524
21009
|
const line = lines[i];
|
|
20525
21010
|
if (line.length === 0) continue;
|
|
20526
21011
|
for (const name of names) {
|
|
20527
|
-
const re = new RegExp(`(^|[^\\w-])${
|
|
21012
|
+
const re = new RegExp(`(^|[^\\w-])${escapeRegExp2(name)}(?![\\w-])`, "i");
|
|
20528
21013
|
if (re.test(line) && !isWrappedAsLinkOrCode(line, name)) {
|
|
20529
21014
|
if (!found.has(name)) found.set(name, true);
|
|
20530
21015
|
}
|
|
@@ -20553,7 +21038,7 @@ function wrapLineReferences(line) {
|
|
|
20553
21038
|
let cursor = 0;
|
|
20554
21039
|
const spans = [];
|
|
20555
21040
|
for (const name of PLUGIN_NAMES) {
|
|
20556
|
-
const re = new RegExp(`(^|[^\\w-])(${
|
|
21041
|
+
const re = new RegExp(`(^|[^\\w-])(${escapeRegExp2(name)})(?![\\w-])`, "gi");
|
|
20557
21042
|
let m;
|
|
20558
21043
|
re.lastIndex = 0;
|
|
20559
21044
|
while ((m = re.exec(line)) !== null) {
|
|
@@ -20774,7 +21259,7 @@ var spec_linker_default = plugin54;
|
|
|
20774
21259
|
|
|
20775
21260
|
// src/template-engine/index.ts
|
|
20776
21261
|
import { readFile as readFile17, writeFile as writeFile6 } from "node:fs/promises";
|
|
20777
|
-
import { isAbsolute as
|
|
21262
|
+
import { isAbsolute as isAbsolute26 } from "node:path";
|
|
20778
21263
|
var API_VERSION36 = "^0.1.10";
|
|
20779
21264
|
var templates = /* @__PURE__ */ new Map();
|
|
20780
21265
|
var MAX_TEMPLATES = 256;
|
|
@@ -20830,7 +21315,7 @@ function renderTemplateRaw(template, variables) {
|
|
|
20830
21315
|
return result;
|
|
20831
21316
|
}
|
|
20832
21317
|
function validateRelativeTemplatePath(field, value) {
|
|
20833
|
-
if (
|
|
21318
|
+
if (isAbsolute26(value) || value.split(/[\\/]+/).includes("..")) {
|
|
20834
21319
|
return `${field} must be a relative path without ".." components`;
|
|
20835
21320
|
}
|
|
20836
21321
|
if (!withinProject(value)) {
|
|
@@ -21430,7 +21915,7 @@ var test_coverage_gate_default = plugin56;
|
|
|
21430
21915
|
import { execFile as execFile14 } from "node:child_process";
|
|
21431
21916
|
import { readFileSync as readFileSync15 } from "node:fs";
|
|
21432
21917
|
import { createRequire as createRequire3 } from "node:module";
|
|
21433
|
-
import { dirname as dirname8, isAbsolute as
|
|
21918
|
+
import { dirname as dirname8, isAbsolute as isAbsolute27, relative as relative26, resolve as resolve27 } from "node:path";
|
|
21434
21919
|
var API_VERSION38 = "^0.1.10";
|
|
21435
21920
|
var state52 = {
|
|
21436
21921
|
invocationCount: 0,
|
|
@@ -21499,17 +21984,17 @@ var ALLOWED_RUNNER_FLAGS = /* @__PURE__ */ new Set([
|
|
|
21499
21984
|
"--reporter=verbose",
|
|
21500
21985
|
"--reporter=default"
|
|
21501
21986
|
]);
|
|
21502
|
-
function
|
|
21987
|
+
function withinProject7(p) {
|
|
21503
21988
|
if (p.length === 0 || p.length > 4096 || p.startsWith("-")) return false;
|
|
21504
21989
|
const root = resolve27(process.cwd());
|
|
21505
|
-
const resolved =
|
|
21506
|
-
const rel =
|
|
21507
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
21990
|
+
const resolved = isAbsolute27(p) ? resolve27(p) : resolve27(root, p);
|
|
21991
|
+
const rel = relative26(root, resolved);
|
|
21992
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute27(rel);
|
|
21508
21993
|
}
|
|
21509
21994
|
function isInside3(parent, child) {
|
|
21510
21995
|
if (parent === child) return true;
|
|
21511
|
-
const rel =
|
|
21512
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
21996
|
+
const rel = relative26(parent, child);
|
|
21997
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute27(rel);
|
|
21513
21998
|
}
|
|
21514
21999
|
function tokenizeCommand(command) {
|
|
21515
22000
|
const trimmed = command.trim();
|
|
@@ -21553,7 +22038,7 @@ function resolveTestCommand(baseCommand, testPattern) {
|
|
|
21553
22038
|
if (!relativeBin) return null;
|
|
21554
22039
|
const packageDir = dirname8(packagePath);
|
|
21555
22040
|
const candidate = resolve27(packageDir, relativeBin);
|
|
21556
|
-
if (
|
|
22041
|
+
if (isAbsolute27(relativeBin) || !isInside3(packageDir, candidate)) {
|
|
21557
22042
|
return null;
|
|
21558
22043
|
}
|
|
21559
22044
|
resolvedEntry = candidate;
|
|
@@ -21562,7 +22047,7 @@ function resolveTestCommand(baseCommand, testPattern) {
|
|
|
21562
22047
|
}
|
|
21563
22048
|
const args = [resolvedEntry, ...runnerArgs];
|
|
21564
22049
|
if (testPattern) {
|
|
21565
|
-
if (!
|
|
22050
|
+
if (!withinProject7(testPattern)) return null;
|
|
21566
22051
|
args.push(testPattern);
|
|
21567
22052
|
}
|
|
21568
22053
|
return {
|
|
@@ -21796,7 +22281,7 @@ var test_flake_detector_default = plugin57;
|
|
|
21796
22281
|
|
|
21797
22282
|
// src/test-generator/index.ts
|
|
21798
22283
|
import { readFileSync as readFileSync16 } from "node:fs";
|
|
21799
|
-
import { isAbsolute as
|
|
22284
|
+
import { isAbsolute as isAbsolute28, relative as relative27, resolve as resolve28 } from "node:path";
|
|
21800
22285
|
var API_VERSION39 = "^0.1.10";
|
|
21801
22286
|
var state53 = {
|
|
21802
22287
|
generateCount: 0,
|
|
@@ -21852,21 +22337,21 @@ var SOURCE_EXTENSIONS = [
|
|
|
21852
22337
|
".cpp",
|
|
21853
22338
|
".hpp"
|
|
21854
22339
|
];
|
|
21855
|
-
function
|
|
22340
|
+
function withinProject8(p) {
|
|
21856
22341
|
if (typeof p !== "string" || p.length === 0 || p.length > 4096) return false;
|
|
21857
22342
|
const root = process.cwd();
|
|
21858
|
-
const resolved =
|
|
21859
|
-
const rel =
|
|
22343
|
+
const resolved = isAbsolute28(p) ? resolve28(p) : resolve28(root, p);
|
|
22344
|
+
const rel = relative27(root, resolved);
|
|
21860
22345
|
if (rel === "" || rel === ".") return true;
|
|
21861
22346
|
if (rel.startsWith("..")) return false;
|
|
21862
|
-
if (
|
|
22347
|
+
if (isAbsolute28(rel)) return false;
|
|
21863
22348
|
return true;
|
|
21864
22349
|
}
|
|
21865
22350
|
function toPosix8(p) {
|
|
21866
22351
|
return p.replace(/\\/g, "/");
|
|
21867
22352
|
}
|
|
21868
22353
|
function relativePath8(p) {
|
|
21869
|
-
return toPosix8(
|
|
22354
|
+
return toPosix8(relative27(process.cwd(), p));
|
|
21870
22355
|
}
|
|
21871
22356
|
function detectExports(content) {
|
|
21872
22357
|
const exports = [];
|
|
@@ -22076,7 +22561,7 @@ var plugin58 = {
|
|
|
22076
22561
|
if (!rawPath || typeof rawPath !== "string") {
|
|
22077
22562
|
return { ok: false, error: "path is required" };
|
|
22078
22563
|
}
|
|
22079
|
-
if (!
|
|
22564
|
+
if (!withinProject8(rawPath)) {
|
|
22080
22565
|
return { ok: false, error: "path is outside the project root" };
|
|
22081
22566
|
}
|
|
22082
22567
|
if (!SOURCE_EXTENSIONS.some((ext) => rawPath.toLowerCase().endsWith(ext))) {
|
|
@@ -22172,7 +22657,7 @@ var test_generator_default = plugin58;
|
|
|
22172
22657
|
// src/test-runner-gate/index.ts
|
|
22173
22658
|
import { execFile as execFile15 } from "node:child_process";
|
|
22174
22659
|
import { access as access5 } from "node:fs/promises";
|
|
22175
|
-
import { basename as basename7, dirname as dirname9, isAbsolute as
|
|
22660
|
+
import { basename as basename7, dirname as dirname9, isAbsolute as isAbsolute29, join as join8 } from "node:path";
|
|
22176
22661
|
import { buildWin32CmdShimInvocation as buildWin32CmdShimInvocation2, resolveWin32Command as resolveWin32Command2 } from "@wrongstack/tools/win32";
|
|
22177
22662
|
function resolveExec(command, args) {
|
|
22178
22663
|
const resolved = resolveWin32Command2(command);
|
|
@@ -22341,7 +22826,7 @@ function resolveAllowedCommand2(customCommand) {
|
|
|
22341
22826
|
if (ALLOWED_COMMAND_TOKENS.has(head)) {
|
|
22342
22827
|
return { cmd: head, args: tokens.slice(1) };
|
|
22343
22828
|
}
|
|
22344
|
-
if (
|
|
22829
|
+
if (isAbsolute29(head)) {
|
|
22345
22830
|
if (!withinProject(head)) return null;
|
|
22346
22831
|
const base = basename7(head);
|
|
22347
22832
|
if (ALLOWED_COMMAND_TOKENS.has(base)) {
|