@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/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/runtime/index.ts
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
4
|
-
import { basename, extname as extname2, isAbsolute as
|
|
4
|
+
import { basename, extname as extname2, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve3 } from "node:path";
|
|
5
5
|
|
|
6
6
|
// src/runtime/llm.ts
|
|
7
7
|
function stripOuterMarkdownFence(text) {
|
|
@@ -347,6 +347,162 @@ function releaseHandles(state, keys) {
|
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
349
|
|
|
350
|
+
// src/runtime/redos-guard.ts
|
|
351
|
+
import { Worker } from "node:worker_threads";
|
|
352
|
+
function withReDoSGuard(re, input, budgetMs = 50, options = {}) {
|
|
353
|
+
const opts = { budgetMs, ...options };
|
|
354
|
+
const start = Date.now();
|
|
355
|
+
const workerSource = buildWorkerSource(re.source, input, re.flags);
|
|
356
|
+
const worker = new Worker(workerSource, {
|
|
357
|
+
eval: true,
|
|
358
|
+
name: `redos-guard:${re.source.slice(0, 32)}`
|
|
359
|
+
});
|
|
360
|
+
return new Promise((resolve4) => {
|
|
361
|
+
let settled = false;
|
|
362
|
+
const onMessage = (msg) => {
|
|
363
|
+
if (settled) return;
|
|
364
|
+
settled = true;
|
|
365
|
+
clearTimeout(timer);
|
|
366
|
+
worker.terminate().catch(() => {
|
|
367
|
+
});
|
|
368
|
+
if (!msg.ok) {
|
|
369
|
+
resolve4({ timedOut: true, match: null });
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
resolve4({ timedOut: false, match: msg.match });
|
|
373
|
+
};
|
|
374
|
+
const onError = () => {
|
|
375
|
+
if (settled) return;
|
|
376
|
+
settled = true;
|
|
377
|
+
clearTimeout(timer);
|
|
378
|
+
worker.terminate().catch(() => {
|
|
379
|
+
});
|
|
380
|
+
resolve4({ timedOut: true, match: null });
|
|
381
|
+
};
|
|
382
|
+
const timer = setTimeout(() => {
|
|
383
|
+
if (settled) return;
|
|
384
|
+
settled = true;
|
|
385
|
+
const elapsedMs = Date.now() - start;
|
|
386
|
+
worker.terminate().catch(() => {
|
|
387
|
+
});
|
|
388
|
+
try {
|
|
389
|
+
opts.onTimeout?.({
|
|
390
|
+
regex: re,
|
|
391
|
+
input,
|
|
392
|
+
budgetMs: opts.budgetMs,
|
|
393
|
+
elapsedMs
|
|
394
|
+
});
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
resolve4({ timedOut: true, match: null });
|
|
398
|
+
}, opts.budgetMs);
|
|
399
|
+
timer.unref?.();
|
|
400
|
+
worker.on("message", onMessage);
|
|
401
|
+
worker.on("error", onError);
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
function buildWorkerSource(source, input, flags) {
|
|
405
|
+
const S = JSON.stringify(source);
|
|
406
|
+
const I = JSON.stringify(input);
|
|
407
|
+
const F = JSON.stringify(flags);
|
|
408
|
+
return `
|
|
409
|
+
const { parentPort } = require('node:worker_threads');
|
|
410
|
+
const source = ${S};
|
|
411
|
+
const input = ${I};
|
|
412
|
+
const flags = ${F};
|
|
413
|
+
try {
|
|
414
|
+
const re = new RegExp(source, flags);
|
|
415
|
+
const match = re.exec(input);
|
|
416
|
+
// parentPort.postMessage, NOT bare postMessage: with eval:true
|
|
417
|
+
// workers this Node version does not expose the bare postMessage
|
|
418
|
+
// global \u2014 the worker throws ReferenceError at startup and the
|
|
419
|
+
// host misreads it as a timeout (positive-path regression).
|
|
420
|
+
parentPort.postMessage({ ok: true, match });
|
|
421
|
+
} catch (err) {
|
|
422
|
+
parentPort.postMessage({ ok: false, error: err && err.message ? err.message : String(err) });
|
|
423
|
+
}
|
|
424
|
+
`;
|
|
425
|
+
}
|
|
426
|
+
function guardedMatcher(re, budgetMs = 50, onTimeout) {
|
|
427
|
+
return (input) => withReDoSGuard(re, input, budgetMs, onTimeout ? { onTimeout } : {});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// src/runtime/sandbox.ts
|
|
431
|
+
import { realpathSync } from "node:fs";
|
|
432
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
|
|
433
|
+
var MAX_PATH_BYTES = 4096;
|
|
434
|
+
function safePath(input, options = {}) {
|
|
435
|
+
if (typeof input !== "string") return null;
|
|
436
|
+
if (input.length === 0 || input.length > MAX_PATH_BYTES) return null;
|
|
437
|
+
if (input.startsWith("-")) return null;
|
|
438
|
+
const projectRoot = resolve2(options.projectRoot ?? process.cwd());
|
|
439
|
+
const lexical = isAbsolute2(input) ? resolve2(input) : resolve2(projectRoot, input);
|
|
440
|
+
if (!withinLexical(projectRoot, lexical)) return null;
|
|
441
|
+
if (options.followSymlinks !== false) {
|
|
442
|
+
let real;
|
|
443
|
+
try {
|
|
444
|
+
real = realpathSync(lexical);
|
|
445
|
+
} catch {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
if (!withinLexical(projectRoot, real)) return null;
|
|
449
|
+
return real;
|
|
450
|
+
}
|
|
451
|
+
return lexical;
|
|
452
|
+
}
|
|
453
|
+
function withinLexical(projectRoot, candidate) {
|
|
454
|
+
const rel = relative2(projectRoot, candidate);
|
|
455
|
+
if (rel === "" || rel === ".") return true;
|
|
456
|
+
if (rel.startsWith("..")) return false;
|
|
457
|
+
if (isAbsolute2(rel)) return false;
|
|
458
|
+
return true;
|
|
459
|
+
}
|
|
460
|
+
function isInsideProject(input, options = {}) {
|
|
461
|
+
return safePath(input, options) !== null;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/runtime/h1-state.ts
|
|
465
|
+
function createH1State(initial) {
|
|
466
|
+
const handles = /* @__PURE__ */ new Map();
|
|
467
|
+
const safeRelease = (unregister) => {
|
|
468
|
+
try {
|
|
469
|
+
unregister();
|
|
470
|
+
} catch {
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
return {
|
|
474
|
+
state: initial,
|
|
475
|
+
register(key, unregister) {
|
|
476
|
+
const prior = handles.get(key);
|
|
477
|
+
if (prior) {
|
|
478
|
+
safeRelease(prior);
|
|
479
|
+
handles.delete(key);
|
|
480
|
+
}
|
|
481
|
+
if (unregister) {
|
|
482
|
+
handles.set(key, unregister);
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
release(key) {
|
|
486
|
+
const prior = handles.get(key);
|
|
487
|
+
if (!prior) return;
|
|
488
|
+
handles.delete(key);
|
|
489
|
+
safeRelease(prior);
|
|
490
|
+
},
|
|
491
|
+
releaseAll() {
|
|
492
|
+
for (const unregister of handles.values()) {
|
|
493
|
+
safeRelease(unregister);
|
|
494
|
+
}
|
|
495
|
+
handles.clear();
|
|
496
|
+
},
|
|
497
|
+
size() {
|
|
498
|
+
return handles.size;
|
|
499
|
+
},
|
|
500
|
+
keys() {
|
|
501
|
+
return [...handles.keys()];
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
350
506
|
// src/runtime/index.ts
|
|
351
507
|
var META_CHARS = /["'`;&|<>\r\n]/;
|
|
352
508
|
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
@@ -361,9 +517,9 @@ function safeSplit(command) {
|
|
|
361
517
|
function withinProjectPath(projectRoot, candidate) {
|
|
362
518
|
if (candidate.length === 0 || candidate.length > 4096) return false;
|
|
363
519
|
if (hasLeadingDash(candidate)) return false;
|
|
364
|
-
const resolved =
|
|
365
|
-
const rel =
|
|
366
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
520
|
+
const resolved = isAbsolute3(candidate) ? resolve3(candidate) : resolve3(projectRoot, candidate);
|
|
521
|
+
const rel = relative3(projectRoot, resolved);
|
|
522
|
+
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
367
523
|
}
|
|
368
524
|
function everyFlagAllowed(allowed, args) {
|
|
369
525
|
for (const arg of args) {
|
|
@@ -375,14 +531,14 @@ function everyFlagAllowed(allowed, args) {
|
|
|
375
531
|
}
|
|
376
532
|
function sanitizeRunnerPath(value, options = {}) {
|
|
377
533
|
if (!value || hasLeadingDash(value)) return null;
|
|
378
|
-
const projectRoot =
|
|
534
|
+
const projectRoot = resolve3(options.projectRoot ?? process.cwd());
|
|
379
535
|
if (!withinProjectPath(projectRoot, value)) return null;
|
|
380
|
-
return
|
|
536
|
+
return isAbsolute3(value) ? resolve3(value) : resolve3(projectRoot, value);
|
|
381
537
|
}
|
|
382
538
|
function resolveRunnerCommand(runtime, command, options = {}) {
|
|
383
539
|
const tokens = safeSplit(command);
|
|
384
540
|
if (!tokens || tokens.length === 0) return null;
|
|
385
|
-
const projectRoot =
|
|
541
|
+
const projectRoot = resolve3(options.projectRoot ?? process.cwd());
|
|
386
542
|
const launcher = runtime.packageManager;
|
|
387
543
|
const [head, second, ...rest] = tokens;
|
|
388
544
|
if (!head) return null;
|
|
@@ -413,7 +569,7 @@ function resolveRunnerCommand(runtime, command, options = {}) {
|
|
|
413
569
|
return { cmd: head, args: [second, exe, ...tail], display };
|
|
414
570
|
}
|
|
415
571
|
}
|
|
416
|
-
if (
|
|
572
|
+
if (isAbsolute3(head)) {
|
|
417
573
|
if (!withinProjectPath(projectRoot, head)) return null;
|
|
418
574
|
const base = basename(head);
|
|
419
575
|
if (base !== runtime.executable && base !== launcher) return null;
|
|
@@ -434,7 +590,7 @@ function runRunnerCommand(argv, options) {
|
|
|
434
590
|
});
|
|
435
591
|
}
|
|
436
592
|
return new Promise((resolvePromise) => {
|
|
437
|
-
const projectRoot =
|
|
593
|
+
const projectRoot = resolve3(options.projectRoot ?? process.cwd());
|
|
438
594
|
const trimmedCwd = options.cwd.trim();
|
|
439
595
|
if (!withinProjectPath(projectRoot, trimmedCwd)) {
|
|
440
596
|
resolvePromise({
|
|
@@ -448,6 +604,7 @@ function runRunnerCommand(argv, options) {
|
|
|
448
604
|
}
|
|
449
605
|
let timedOut = false;
|
|
450
606
|
let spawnErrored = false;
|
|
607
|
+
const start = Date.now();
|
|
451
608
|
const stdoutChunks = [];
|
|
452
609
|
const stderrChunks = [];
|
|
453
610
|
let stdoutBytes = 0;
|
|
@@ -477,6 +634,15 @@ function runRunnerCommand(argv, options) {
|
|
|
477
634
|
timeout: options.timeoutMs,
|
|
478
635
|
signal: options.signal,
|
|
479
636
|
maxBuffer: MAX_BUFFER_BYTES,
|
|
637
|
+
// execFile defaults `encoding` to 'utf8', which makes the
|
|
638
|
+
// stdout/stderr `data` events emit *strings*. The chunk arrays
|
|
639
|
+
// below are typed Buffer[] and every consumer runs them through
|
|
640
|
+
// Buffer.concat(...).toString('utf8'), which throws
|
|
641
|
+
// ERR_INVALID_ARG_TYPE on any non-empty string output. Pin the
|
|
642
|
+
// streams to buffers so the declared contract holds (regression:
|
|
643
|
+
// runRunnerCommand crashed on any child that actually wrote
|
|
644
|
+
// output; only the maxBuffer fixture exercised this path).
|
|
645
|
+
encoding: "buffer",
|
|
480
646
|
windowsHide: true,
|
|
481
647
|
shell: false,
|
|
482
648
|
...invocation.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
|
|
@@ -505,11 +671,31 @@ function runRunnerCommand(argv, options) {
|
|
|
505
671
|
}
|
|
506
672
|
if (err) {
|
|
507
673
|
const anyErr = err;
|
|
674
|
+
if ((anyErr.killed === true || anyErr.signal === "SIGTERM") && // maxBuffer overflow must NOT be misreported as a timeout
|
|
675
|
+
// — it's a real failure (the child wrote too much) and
|
|
676
|
+
// downstream callers (type-gate/index.ts:227) return
|
|
677
|
+
// null on timedOut=true, which would silently swallow
|
|
678
|
+
// maxBuffer overflow into a confusing empty-output
|
|
679
|
+
// result. Skip the timeout resolve when the err shape
|
|
680
|
+
// names maxBuffer explicitly.
|
|
681
|
+
!/maxBuffer length exceeded/i.test(anyErr.message ?? "") && // And only count it as a timeout if the wall clock has
|
|
682
|
+
// actually elapsed past the budget. External SIGTERMs and
|
|
683
|
+
// races against the exit handler don't satisfy this.
|
|
684
|
+
Date.now() - start >= options.timeoutMs) {
|
|
685
|
+
resolvePromise({
|
|
686
|
+
code: null,
|
|
687
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
688
|
+
stderr: Buffer.concat(stderrChunks).toString("utf-8"),
|
|
689
|
+
timedOut: true,
|
|
690
|
+
spawnError: false
|
|
691
|
+
});
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
508
694
|
const code = typeof anyErr.code === "number" ? anyErr.code : 1;
|
|
509
695
|
resolvePromise({
|
|
510
696
|
code,
|
|
511
|
-
stdout: Buffer.concat(stdoutChunks).toString("
|
|
512
|
-
stderr: Buffer.concat(stderrChunks).toString("
|
|
697
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
698
|
+
stderr: Buffer.concat(stderrChunks).toString("utf-8"),
|
|
513
699
|
timedOut: false,
|
|
514
700
|
spawnError: false
|
|
515
701
|
});
|
|
@@ -525,7 +711,7 @@ function runRunnerCommand(argv, options) {
|
|
|
525
711
|
}
|
|
526
712
|
);
|
|
527
713
|
child.on("exit", (_code, signal) => {
|
|
528
|
-
if (signal !== null) {
|
|
714
|
+
if (signal !== null && Date.now() - start >= options.timeoutMs) {
|
|
529
715
|
timedOut = true;
|
|
530
716
|
}
|
|
531
717
|
});
|
|
@@ -555,14 +741,14 @@ async function probeRunner(runtime, probeArg = "--version", options) {
|
|
|
555
741
|
}
|
|
556
742
|
function withinProject(p) {
|
|
557
743
|
const cwd = process.cwd();
|
|
558
|
-
return withinProjectPath(cwd, p) ||
|
|
744
|
+
return withinProjectPath(cwd, p) || relative3(cwd, p) === ".";
|
|
559
745
|
}
|
|
560
746
|
function locateRunnerEntry(runtime, projectRoot) {
|
|
561
|
-
const root =
|
|
747
|
+
const root = resolve3(projectRoot);
|
|
562
748
|
const candidates = [
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
749
|
+
resolve3(root, "node_modules", ".bin", runtime.executable),
|
|
750
|
+
resolve3(root, "node_modules", ".bin", `${runtime.executable}.cmd`),
|
|
751
|
+
resolve3(root, "node_modules", ".bin", `${runtime.executable}.ps1`)
|
|
566
752
|
];
|
|
567
753
|
for (const c of candidates) {
|
|
568
754
|
if (existsSync(c)) return c;
|
|
@@ -592,7 +778,7 @@ function collectSourceFiles(root, opts) {
|
|
|
592
778
|
entries.sort();
|
|
593
779
|
for (const entry of entries) {
|
|
594
780
|
if (excludeSet.has(entry)) continue;
|
|
595
|
-
const full =
|
|
781
|
+
const full = resolve3(dir, entry);
|
|
596
782
|
let st;
|
|
597
783
|
try {
|
|
598
784
|
st = statSync(full);
|
|
@@ -635,7 +821,7 @@ async function collectSourceFilesAsync(root, opts) {
|
|
|
635
821
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
636
822
|
for (const entry of entries) {
|
|
637
823
|
if (excludeSet.has(entry.name)) continue;
|
|
638
|
-
const full =
|
|
824
|
+
const full = resolve3(dir, entry.name);
|
|
639
825
|
if (entry.isDirectory()) {
|
|
640
826
|
await walk(full, depth + 1);
|
|
641
827
|
} else if (entry.isFile() && matchesExtension(full, opts.extensions)) {
|
|
@@ -656,7 +842,10 @@ export {
|
|
|
656
842
|
clearLocalBinCache,
|
|
657
843
|
collectSourceFiles,
|
|
658
844
|
collectSourceFilesAsync,
|
|
845
|
+
createH1State,
|
|
659
846
|
findOnPath,
|
|
847
|
+
guardedMatcher,
|
|
848
|
+
isInsideProject,
|
|
660
849
|
locateRunnerEntry,
|
|
661
850
|
matchesExtension,
|
|
662
851
|
parseLlmJsonObject,
|
|
@@ -672,7 +861,9 @@ export {
|
|
|
672
861
|
runOptionalPluginLlm,
|
|
673
862
|
runRunnerCommand,
|
|
674
863
|
safeJsonStringify,
|
|
864
|
+
safePath,
|
|
675
865
|
sanitizeRunnerPath,
|
|
676
866
|
stripOuterMarkdownFence,
|
|
867
|
+
withReDoSGuard,
|
|
677
868
|
withinProject
|
|
678
869
|
};
|
package/dist/secret-scanner.js
CHANGED
|
@@ -188,6 +188,7 @@ function createState() {
|
|
|
188
188
|
allowCount: 0,
|
|
189
189
|
/** PostToolUse: secrets detected in tool output. */
|
|
190
190
|
leakCount: 0,
|
|
191
|
+
timeoutCount: 0,
|
|
191
192
|
/** Most recent PreToolUse block — surfaced by `secret_scanner_status`. */
|
|
192
193
|
lastBlock: null,
|
|
193
194
|
/** Most recent PostToolUse leak — surfaced by `secret_scanner_status`. */
|
|
@@ -356,7 +357,19 @@ function buildHook(cfg, log, runtime) {
|
|
|
356
357
|
const { state } = runtime;
|
|
357
358
|
if (!cfg.enabled) return;
|
|
358
359
|
const toolName = input.toolName ?? "unknown";
|
|
359
|
-
|
|
360
|
+
let matched;
|
|
361
|
+
try {
|
|
362
|
+
matched = scanInput(input.toolInput);
|
|
363
|
+
} catch (err) {
|
|
364
|
+
if (String(err).includes("ReDoS")) {
|
|
365
|
+
state.timeoutCount += 1;
|
|
366
|
+
return {
|
|
367
|
+
decision: "block",
|
|
368
|
+
reason: "secret-scanner: ReDoS timeout \u2014 regex scan exceeded the wall-clock budget. Fail-closed: treated as a block."
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
throw err;
|
|
372
|
+
}
|
|
360
373
|
if (!matched) return;
|
|
361
374
|
const summary = matched.join(", ");
|
|
362
375
|
const when = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -534,7 +547,8 @@ var plugin = {
|
|
|
534
547
|
block: state.blockCount,
|
|
535
548
|
redact: state.redactCount,
|
|
536
549
|
allow: state.allowCount,
|
|
537
|
-
leak: state.leakCount
|
|
550
|
+
leak: state.leakCount,
|
|
551
|
+
timeoutCount: state.timeoutCount
|
|
538
552
|
},
|
|
539
553
|
lastBlock: state.lastBlock,
|
|
540
554
|
lastLeak: state.lastLeak
|
package/dist/spec-linker.js
CHANGED
|
@@ -55,10 +55,10 @@ var OFFICIAL_PLUGIN_NAMES = [
|
|
|
55
55
|
"notify-hub",
|
|
56
56
|
"changelog-writer",
|
|
57
57
|
"injection-shield",
|
|
58
|
+
"prompt-firewall",
|
|
58
59
|
"llm-cache",
|
|
59
60
|
"model-router",
|
|
60
61
|
"pr-drafter",
|
|
61
|
-
"prompt-firewall",
|
|
62
62
|
"auto-escalate",
|
|
63
63
|
"test-coverage-gate",
|
|
64
64
|
"type-gate",
|
package/dist/type-gate.js
CHANGED
|
@@ -177,6 +177,7 @@ function runRunnerCommand(argv, options) {
|
|
|
177
177
|
}
|
|
178
178
|
let timedOut = false;
|
|
179
179
|
let spawnErrored = false;
|
|
180
|
+
const start = Date.now();
|
|
180
181
|
const stdoutChunks = [];
|
|
181
182
|
const stderrChunks = [];
|
|
182
183
|
let stdoutBytes = 0;
|
|
@@ -206,6 +207,15 @@ function runRunnerCommand(argv, options) {
|
|
|
206
207
|
timeout: options.timeoutMs,
|
|
207
208
|
signal: options.signal,
|
|
208
209
|
maxBuffer: MAX_BUFFER_BYTES,
|
|
210
|
+
// execFile defaults `encoding` to 'utf8', which makes the
|
|
211
|
+
// stdout/stderr `data` events emit *strings*. The chunk arrays
|
|
212
|
+
// below are typed Buffer[] and every consumer runs them through
|
|
213
|
+
// Buffer.concat(...).toString('utf8'), which throws
|
|
214
|
+
// ERR_INVALID_ARG_TYPE on any non-empty string output. Pin the
|
|
215
|
+
// streams to buffers so the declared contract holds (regression:
|
|
216
|
+
// runRunnerCommand crashed on any child that actually wrote
|
|
217
|
+
// output; only the maxBuffer fixture exercised this path).
|
|
218
|
+
encoding: "buffer",
|
|
209
219
|
windowsHide: true,
|
|
210
220
|
shell: false,
|
|
211
221
|
...invocation.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
|
|
@@ -234,11 +244,31 @@ function runRunnerCommand(argv, options) {
|
|
|
234
244
|
}
|
|
235
245
|
if (err) {
|
|
236
246
|
const anyErr = err;
|
|
247
|
+
if ((anyErr.killed === true || anyErr.signal === "SIGTERM") && // maxBuffer overflow must NOT be misreported as a timeout
|
|
248
|
+
// — it's a real failure (the child wrote too much) and
|
|
249
|
+
// downstream callers (type-gate/index.ts:227) return
|
|
250
|
+
// null on timedOut=true, which would silently swallow
|
|
251
|
+
// maxBuffer overflow into a confusing empty-output
|
|
252
|
+
// result. Skip the timeout resolve when the err shape
|
|
253
|
+
// names maxBuffer explicitly.
|
|
254
|
+
!/maxBuffer length exceeded/i.test(anyErr.message ?? "") && // And only count it as a timeout if the wall clock has
|
|
255
|
+
// actually elapsed past the budget. External SIGTERMs and
|
|
256
|
+
// races against the exit handler don't satisfy this.
|
|
257
|
+
Date.now() - start >= options.timeoutMs) {
|
|
258
|
+
resolvePromise({
|
|
259
|
+
code: null,
|
|
260
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
261
|
+
stderr: Buffer.concat(stderrChunks).toString("utf-8"),
|
|
262
|
+
timedOut: true,
|
|
263
|
+
spawnError: false
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
237
267
|
const code = typeof anyErr.code === "number" ? anyErr.code : 1;
|
|
238
268
|
resolvePromise({
|
|
239
269
|
code,
|
|
240
|
-
stdout: Buffer.concat(stdoutChunks).toString("
|
|
241
|
-
stderr: Buffer.concat(stderrChunks).toString("
|
|
270
|
+
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
271
|
+
stderr: Buffer.concat(stderrChunks).toString("utf-8"),
|
|
242
272
|
timedOut: false,
|
|
243
273
|
spawnError: false
|
|
244
274
|
});
|
|
@@ -254,7 +284,7 @@ function runRunnerCommand(argv, options) {
|
|
|
254
284
|
}
|
|
255
285
|
);
|
|
256
286
|
child.on("exit", (_code, signal) => {
|
|
257
|
-
if (signal !== null) {
|
|
287
|
+
if (signal !== null && Date.now() - start >= options.timeoutMs) {
|
|
258
288
|
timedOut = true;
|
|
259
289
|
}
|
|
260
290
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/plugins",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.308.1",
|
|
4
4
|
"description": "Official WrongStack collection of focused plugins for code quality, security, observability, planning, and agent coordination",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ECOSTACK TECHNOLOGY OÜ",
|
|
@@ -168,6 +168,10 @@
|
|
|
168
168
|
"types": "./dist/injection-shield.d.ts",
|
|
169
169
|
"import": "./dist/injection-shield.js"
|
|
170
170
|
},
|
|
171
|
+
"./prompt-firewall": {
|
|
172
|
+
"types": "./dist/prompt-firewall.d.ts",
|
|
173
|
+
"import": "./dist/prompt-firewall.js"
|
|
174
|
+
},
|
|
171
175
|
"./llm-cache": {
|
|
172
176
|
"types": "./dist/llm-cache.d.ts",
|
|
173
177
|
"import": "./dist/llm-cache.js"
|
|
@@ -180,10 +184,6 @@
|
|
|
180
184
|
"types": "./dist/pr-drafter.d.ts",
|
|
181
185
|
"import": "./dist/pr-drafter.js"
|
|
182
186
|
},
|
|
183
|
-
"./prompt-firewall": {
|
|
184
|
-
"types": "./dist/prompt-firewall.d.ts",
|
|
185
|
-
"import": "./dist/prompt-firewall.js"
|
|
186
|
-
},
|
|
187
187
|
"./auto-escalate": {
|
|
188
188
|
"types": "./dist/auto-escalate.d.ts",
|
|
189
189
|
"import": "./dist/auto-escalate.js"
|
|
@@ -303,8 +303,8 @@
|
|
|
303
303
|
"vitest": "^4.1.10"
|
|
304
304
|
},
|
|
305
305
|
"dependencies": {
|
|
306
|
-
"@wrongstack/
|
|
307
|
-
"@wrongstack/
|
|
306
|
+
"@wrongstack/core": "0.308.1",
|
|
307
|
+
"@wrongstack/tools": "0.308.1"
|
|
308
308
|
},
|
|
309
309
|
"scripts": {
|
|
310
310
|
"build": "node ../../scripts/build-package.mjs",
|