@wrongstack/tools 0.305.0 → 0.306.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_shell-pick.d.ts +4 -5
- package/dist/_util.d.ts +22 -5
- package/dist/audit.d.ts +0 -1
- package/dist/audit.js +135 -46
- package/dist/bash.js +61 -37
- package/dist/browser/index.js +29 -9
- package/dist/browser/types.d.ts +7 -1
- package/dist/builtin.js +1280 -728
- package/dist/codebase-index/codebase-search-tool.d.ts +5 -0
- package/dist/codebase-index/index.d.ts +1 -0
- package/dist/codebase-index/index.js +225 -153
- package/dist/codebase-index/project-server-endpoint.d.ts +1 -2
- package/dist/codebase-index/project-server.js +19 -20
- package/dist/diff.d.ts +5 -0
- package/dist/diff.js +78 -12
- package/dist/document.js +18 -6
- package/dist/edit.js +69 -16
- package/dist/exec.js +44 -22
- package/dist/fetch.js +13 -1
- package/dist/format.d.ts +4 -2
- package/dist/format.js +81 -31
- package/dist/glob.js +12 -4
- package/dist/grep.d.ts +2 -0
- package/dist/grep.js +15 -4
- package/dist/index.js +1357 -765
- package/dist/install.js +96 -37
- package/dist/kanban-tool-types.d.ts +6 -1
- package/dist/kanban.js +60 -0
- package/dist/languages/index.js +28 -13
- package/dist/lint.js +28 -13
- package/dist/logs.d.ts +0 -1
- package/dist/logs.js +44 -13
- package/dist/memory.d.ts +8 -0
- package/dist/memory.js +23 -3
- package/dist/mode.d.ts +1 -1
- package/dist/mode.js +3 -0
- package/dist/next-steps.d.ts +2 -3
- package/dist/next-steps.js +3 -3
- package/dist/outdated.d.ts +0 -3
- package/dist/outdated.js +89 -48
- package/dist/pack.js +1280 -728
- package/dist/plan.js +76 -3
- package/dist/process-registry.d.ts +8 -2
- package/dist/process-registry.js +28 -13
- package/dist/ps-slash.js +22 -12
- package/dist/read.js +10 -4
- package/dist/replace.d.ts +4 -0
- package/dist/replace.js +104 -7
- package/dist/search.d.ts +6 -0
- package/dist/search.js +47 -26
- package/dist/session-kanban.d.ts +25 -0
- package/dist/session-kanban.js +16 -0
- package/dist/skill.d.ts +6 -0
- package/dist/skill.js +9 -10
- package/dist/task.js +66 -2
- package/dist/test.js +28 -13
- package/dist/todo.js +64 -2
- package/dist/tool-icons.js +4 -2
- package/dist/tool-summary.d.ts +1 -1
- package/dist/tool-summary.js +76 -1
- package/dist/tool-tier.js +1280 -728
- package/dist/tree.js +9 -10
- package/dist/typecheck.d.ts +0 -2
- package/dist/typecheck.js +98 -31
- package/dist/write.js +58 -10
- package/package.json +4 -3
package/dist/_shell-pick.d.ts
CHANGED
|
@@ -77,11 +77,10 @@ export declare function pickShell(platform: NodeJS.Platform, command: string, en
|
|
|
77
77
|
* `-replace`, `-split`, `-join`, `-is`, `-as`, `-f`).
|
|
78
78
|
* - `.ps1` extension mentioned in the command.
|
|
79
79
|
* - `&` call operator followed by a `$`-variable (`& $cmd ...`).
|
|
80
|
-
* -
|
|
81
|
-
* `
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* `where`, `?`, `%`.
|
|
80
|
+
* - PowerShell-only aliases: `gci`, `gi`, `gp`, `gcm`, `gps`. Unix-style
|
|
81
|
+
* names (`ls`, `cat`, `cp`, `mv`, `rm`, `sl`) are deliberately NOT
|
|
82
|
+
* treated as PowerShell tells — they are Git-Bash/MSYS-normal on
|
|
83
|
+
* Windows and their PS alias semantics differ (`rm -rf` fails in PS).
|
|
85
84
|
* - `#requires` directive at start of script.
|
|
86
85
|
* - `param()` block at start of script.
|
|
87
86
|
*
|
package/dist/_util.d.ts
CHANGED
|
@@ -8,12 +8,19 @@ export declare function sha256hex(content: string): string;
|
|
|
8
8
|
/** Detected package manager for a project directory. */
|
|
9
9
|
export type PackageManager = 'pnpm' | 'yarn' | 'npm';
|
|
10
10
|
/**
|
|
11
|
-
* Detect the project's package manager
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Detect the project's package manager.
|
|
12
|
+
*
|
|
13
|
+
* Per directory, precedence is: `package.json#packageManager` (authoritative
|
|
14
|
+
* when declared) → `pnpm-lock.yaml` → `yarn.lock` → `bun.lockb`/`bun.lock`
|
|
15
|
+
* (bun is treated as npm-compatible and reported as `npm`) →
|
|
16
|
+
* `package-lock.json`/`npm-shrinkwrap.json`. When `stopAt` (usually the
|
|
17
|
+
* project root) is provided and `cwd` is nested inside it, parent directories
|
|
18
|
+
* are walked up to and including `stopAt` — monorepo packages rarely carry
|
|
19
|
+
* their own lockfile. Missing or unreadable directories fall back to `npm`
|
|
20
|
+
* rather than throwing, so a `safeResolve`-checked cwd that happens to be
|
|
21
|
+
* empty never aborts the tool.
|
|
15
22
|
*/
|
|
16
|
-
export declare function detectPackageManager(cwd: string): Promise<PackageManager>;
|
|
23
|
+
export declare function detectPackageManager(cwd: string, stopAt?: string): Promise<PackageManager>;
|
|
17
24
|
export declare function resolvePath(input: string, ctx: Context): string;
|
|
18
25
|
export declare function ensureInsideRoot(absPath: string, ctx: Context): string;
|
|
19
26
|
export declare function safeResolve(input: string, ctx: Context): string;
|
|
@@ -62,6 +69,16 @@ export declare function resolveRealInsideRoot(absPath: string, ctx: Context): Pr
|
|
|
62
69
|
* caller then opened.
|
|
63
70
|
*/
|
|
64
71
|
export declare function safeResolveReal(input: string, ctx: Context): Promise<string>;
|
|
72
|
+
/**
|
|
73
|
+
* Truncate a diff (or similar text payload) to `maxBytes`, cutting at a line
|
|
74
|
+
* boundary and appending an explicit marker. Used by the mutating file tools
|
|
75
|
+
* (`write`, `edit`, `replace`) so their returned diffs stay inside the tool's
|
|
76
|
+
* declared `maxOutputBytes` budget instead of relying on downstream clipping.
|
|
77
|
+
*/
|
|
78
|
+
export declare function truncateDiffPayload(diff: string, maxBytes: number): {
|
|
79
|
+
text: string;
|
|
80
|
+
truncated: boolean;
|
|
81
|
+
};
|
|
65
82
|
export declare function truncateMiddle(s: string, max: number): string;
|
|
66
83
|
export declare function isBinaryBuffer(buf: Buffer): boolean;
|
|
67
84
|
/** Unified byte cap for all command tool output fed to the model. */
|
package/dist/audit.d.ts
CHANGED
package/dist/audit.js
CHANGED
|
@@ -364,8 +364,11 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
364
364
|
/--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
|
|
365
365
|
// -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
|
|
366
366
|
// (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
|
|
367
|
+
// The value must be token-like (>= 8 chars) so ordinary combined flags such
|
|
368
|
+
// as `tar -tf` / `ssh -tt` are not eaten. Global flag: EVERY occurrence is
|
|
369
|
+
// redacted, not just the first.
|
|
367
370
|
// NOTE: synced with @wrongstack/core observability/redact-command.ts.
|
|
368
|
-
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]
|
|
371
|
+
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]{8,}/g,
|
|
369
372
|
// -p|-password|-a (redis auth) short flags: attached + separated + =value.
|
|
370
373
|
// Same token-start anchor; over-redaction is an accepted tradeoff for a
|
|
371
374
|
// redaction function. Synced with core copy.
|
|
@@ -373,8 +376,9 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
373
376
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
374
377
|
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
375
378
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
376
|
-
// when preceded by a flag name (e.g. --github-token=EyJ...).
|
|
377
|
-
|
|
379
|
+
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
380
|
+
// every such flag in the command line is redacted, not just the first.
|
|
381
|
+
/--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/g
|
|
378
382
|
];
|
|
379
383
|
function redactCommand(cmd) {
|
|
380
384
|
let result = cmd;
|
|
@@ -463,11 +467,15 @@ var ProcessRegistryImpl = class {
|
|
|
463
467
|
return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;
|
|
464
468
|
}
|
|
465
469
|
_canSignalProcessGroup(p) {
|
|
466
|
-
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
470
|
+
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && p.child !== null && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
467
471
|
}
|
|
468
472
|
_killChildDirect(p, signal) {
|
|
469
473
|
try {
|
|
470
|
-
p.child
|
|
474
|
+
if (p.child) {
|
|
475
|
+
p.child.kill(signal);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (this._isSafeSignalPid(p.pid)) process.kill(p.pid, signal);
|
|
471
479
|
} catch {
|
|
472
480
|
}
|
|
473
481
|
}
|
|
@@ -665,15 +673,15 @@ var ProcessRegistryImpl = class {
|
|
|
665
673
|
this._pruneStale(pid);
|
|
666
674
|
const p = this.processes.get(pid);
|
|
667
675
|
if (!p) return false;
|
|
668
|
-
if (p.killed) return true;
|
|
676
|
+
if (p.killed && opts.force !== true) return true;
|
|
669
677
|
if (p.protected && opts.includeProtected !== true) return false;
|
|
670
678
|
if (opts.preserveBackground && p.background) return false;
|
|
671
679
|
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
672
680
|
const isWin2 = os.platform() === "win32";
|
|
673
681
|
if (isWin2) {
|
|
674
|
-
const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
|
|
682
|
+
const liveRealChild = p.child === null || p.child.exitCode === null && typeof p.child.pid === "number";
|
|
675
683
|
const directFallback = () => {
|
|
676
|
-
if (p.child.exitCode === null) {
|
|
684
|
+
if (p.child && p.child.exitCode === null) {
|
|
677
685
|
try {
|
|
678
686
|
p.child.kill("SIGKILL");
|
|
679
687
|
} catch {
|
|
@@ -685,10 +693,7 @@ var ProcessRegistryImpl = class {
|
|
|
685
693
|
onSettled: directFallback
|
|
686
694
|
})) {
|
|
687
695
|
} else {
|
|
688
|
-
|
|
689
|
-
p.child.kill(force ? "SIGKILL" : "SIGTERM");
|
|
690
|
-
} catch {
|
|
691
|
-
}
|
|
696
|
+
this._killChildDirect(p, force ? "SIGKILL" : "SIGTERM");
|
|
692
697
|
}
|
|
693
698
|
p.killed = true;
|
|
694
699
|
return true;
|
|
@@ -699,7 +704,7 @@ var ProcessRegistryImpl = class {
|
|
|
699
704
|
} else {
|
|
700
705
|
this._killPosix(p, "SIGTERM");
|
|
701
706
|
const timer = setTimeout(() => {
|
|
702
|
-
if (this.processes.has(pid) && !p.child
|
|
707
|
+
if (this.processes.has(pid) && !p.child?.killed) {
|
|
703
708
|
this._killPosix(p, "SIGKILL");
|
|
704
709
|
}
|
|
705
710
|
}, graceMs);
|
|
@@ -754,6 +759,16 @@ var ProcessRegistryImpl = class {
|
|
|
754
759
|
* before reusing a PID, but we want to clean up before that becomes a risk.
|
|
755
760
|
*/
|
|
756
761
|
_isStaleEntry(entry) {
|
|
762
|
+
if (entry.child === null) {
|
|
763
|
+
if (Date.now() - entry.startedAt <= 6e4) return false;
|
|
764
|
+
if (os.platform() === "win32") return false;
|
|
765
|
+
try {
|
|
766
|
+
process.kill(entry.pid, 0);
|
|
767
|
+
return false;
|
|
768
|
+
} catch (err) {
|
|
769
|
+
return err.code !== "EPERM";
|
|
770
|
+
}
|
|
771
|
+
}
|
|
757
772
|
return entry.child.exitCode !== null && Date.now() - entry.startedAt > 6e4;
|
|
758
773
|
}
|
|
759
774
|
/**
|
|
@@ -1028,19 +1043,48 @@ function utf8Prefix(text, maxBytes) {
|
|
|
1028
1043
|
// src/_util.ts
|
|
1029
1044
|
import * as path3 from "node:path";
|
|
1030
1045
|
import * as Core from "@wrongstack/core/utils";
|
|
1031
|
-
async function detectPackageManager(cwd) {
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1046
|
+
async function detectPackageManager(cwd, stopAt) {
|
|
1047
|
+
let dir = path3.resolve(cwd);
|
|
1048
|
+
const stop = stopAt ? path3.resolve(stopAt) : dir;
|
|
1049
|
+
for (; ; ) {
|
|
1050
|
+
const found = await detectPackageManagerInDir(dir);
|
|
1051
|
+
if (found) return found;
|
|
1052
|
+
if (dir === stop) break;
|
|
1053
|
+
const parent = path3.dirname(dir);
|
|
1054
|
+
const relParent = path3.relative(stop, parent);
|
|
1055
|
+
if (parent === dir || relParent.startsWith("..") || path3.isAbsolute(relParent)) break;
|
|
1056
|
+
dir = parent;
|
|
1037
1057
|
}
|
|
1058
|
+
return "npm";
|
|
1059
|
+
}
|
|
1060
|
+
async function detectPackageManagerInDir(dir) {
|
|
1061
|
+
const fs6 = await import("node:fs/promises");
|
|
1038
1062
|
try {
|
|
1039
|
-
await
|
|
1040
|
-
|
|
1063
|
+
const raw = await fs6.readFile(path3.join(dir, "package.json"), "utf8");
|
|
1064
|
+
const declared = JSON.parse(raw).packageManager;
|
|
1065
|
+
if (typeof declared === "string") {
|
|
1066
|
+
const name = declared.split("@")[0] ?? "";
|
|
1067
|
+
if (name === "pnpm" || name === "yarn") return name;
|
|
1068
|
+
if (name === "npm" || name === "bun") return "npm";
|
|
1069
|
+
}
|
|
1041
1070
|
} catch {
|
|
1042
1071
|
}
|
|
1043
|
-
|
|
1072
|
+
const lockfiles = [
|
|
1073
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
1074
|
+
["yarn.lock", "yarn"],
|
|
1075
|
+
["bun.lockb", "npm"],
|
|
1076
|
+
["bun.lock", "npm"],
|
|
1077
|
+
["package-lock.json", "npm"],
|
|
1078
|
+
["npm-shrinkwrap.json", "npm"]
|
|
1079
|
+
];
|
|
1080
|
+
for (const [file, manager] of lockfiles) {
|
|
1081
|
+
try {
|
|
1082
|
+
await fs6.stat(`${dir}/${file}`);
|
|
1083
|
+
return manager;
|
|
1084
|
+
} catch {
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
return null;
|
|
1044
1088
|
}
|
|
1045
1089
|
function resolvePath(input, ctx) {
|
|
1046
1090
|
return path3.isAbsolute(input) ? path3.normalize(input) : path3.resolve(ctx.workingDir ?? ctx.cwd, input);
|
|
@@ -3798,11 +3842,18 @@ async function tryLegacyPackageOperation(operation, ctx, packages = []) {
|
|
|
3798
3842
|
}
|
|
3799
3843
|
|
|
3800
3844
|
// src/audit.ts
|
|
3845
|
+
var SEVERITY_RANK = {
|
|
3846
|
+
info: 0,
|
|
3847
|
+
low: 1,
|
|
3848
|
+
moderate: 2,
|
|
3849
|
+
high: 3,
|
|
3850
|
+
critical: 4
|
|
3851
|
+
};
|
|
3801
3852
|
var auditTool = {
|
|
3802
3853
|
name: "audit",
|
|
3803
3854
|
category: "Package Management",
|
|
3804
3855
|
description: "Run a security audit against project dependencies (using pnpm/npm audit). Reports known vulnerabilities with severity.",
|
|
3805
|
-
usageHint: "CRITICAL SECURITY TOOL:\n\n- Run regularly and especially before any release.\n- Use `level` to focus on high/critical issues.\n- `
|
|
3856
|
+
usageHint: "CRITICAL SECURITY TOOL:\n\n- Run regularly and especially before any release.\n- Use `level` to focus on high/critical issues.\n- This tool is read-only: to remediate, use `install` (or `language_package`) to upgrade the affected packages.\nThis is one of the most important tools for supply chain security.",
|
|
3806
3857
|
permission: "confirm",
|
|
3807
3858
|
mutating: false,
|
|
3808
3859
|
capabilities: ["shell.restricted"],
|
|
@@ -3817,8 +3868,10 @@ var auditTool = {
|
|
|
3817
3868
|
enum: ["low", "moderate", "high", "critical"],
|
|
3818
3869
|
description: "Minimum severity level to report"
|
|
3819
3870
|
},
|
|
3820
|
-
fix: {
|
|
3821
|
-
|
|
3871
|
+
fix: {
|
|
3872
|
+
type: "boolean",
|
|
3873
|
+
description: "Deprecated and rejected \u2014 this tool is read-only and never modifies dependencies. Use `install` (or `language_package`) to remediate vulnerabilities."
|
|
3874
|
+
}
|
|
3822
3875
|
}
|
|
3823
3876
|
},
|
|
3824
3877
|
async execute(input, ctx, opts) {
|
|
@@ -3832,6 +3885,11 @@ var auditTool = {
|
|
|
3832
3885
|
return final;
|
|
3833
3886
|
},
|
|
3834
3887
|
async *executeStream(input, ctx, opts) {
|
|
3888
|
+
if (input.fix === true) {
|
|
3889
|
+
throw new Error(
|
|
3890
|
+
"audit: `fix: true` is not supported \u2014 this tool is read-only (mutating: false). To remediate vulnerabilities, upgrade the affected packages with the `install` tool (or `language_package` for non-JS ecosystems)."
|
|
3891
|
+
);
|
|
3892
|
+
}
|
|
3835
3893
|
const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
|
|
3836
3894
|
const bridge = await tryLegacyPackageOperation("package-audit", {
|
|
3837
3895
|
cwd,
|
|
@@ -3861,13 +3919,11 @@ var auditTool = {
|
|
|
3861
3919
|
};
|
|
3862
3920
|
return;
|
|
3863
3921
|
}
|
|
3864
|
-
const manager = await detectPackageManager(cwd);
|
|
3922
|
+
const manager = await detectPackageManager(cwd, ctx.projectRoot);
|
|
3865
3923
|
yield { type: "log", text: `Auditing with ${manager}\u2026`, data: { manager } };
|
|
3866
3924
|
const args = ["audit", "--json"];
|
|
3867
|
-
if (input.
|
|
3868
|
-
|
|
3869
|
-
const pkgs = Array.isArray(input.packages) ? input.packages : input.packages.split(",");
|
|
3870
|
-
args.push(...pkgs.map((p) => p.trim()));
|
|
3925
|
+
if (input.level && (manager === "npm" || manager === "pnpm")) {
|
|
3926
|
+
args.push(`--audit-level=${input.level}`);
|
|
3871
3927
|
}
|
|
3872
3928
|
const result = yield* spawnStream({
|
|
3873
3929
|
cmd: manager,
|
|
@@ -3876,10 +3932,16 @@ var auditTool = {
|
|
|
3876
3932
|
signal: opts.signal,
|
|
3877
3933
|
maxBytes: 1e5
|
|
3878
3934
|
});
|
|
3879
|
-
yield {
|
|
3935
|
+
yield {
|
|
3936
|
+
type: "final",
|
|
3937
|
+
output: parseAuditOutput(result.stdout, result.exitCode, {
|
|
3938
|
+
level: input.level,
|
|
3939
|
+
spawnTruncated: result.truncated
|
|
3940
|
+
})
|
|
3941
|
+
};
|
|
3880
3942
|
}
|
|
3881
3943
|
};
|
|
3882
|
-
function parseAuditOutput(json, exitCode) {
|
|
3944
|
+
function parseAuditOutput(json, exitCode, opts = {}) {
|
|
3883
3945
|
if (!json) {
|
|
3884
3946
|
return {
|
|
3885
3947
|
exit_code: exitCode,
|
|
@@ -3890,18 +3952,14 @@ function parseAuditOutput(json, exitCode) {
|
|
|
3890
3952
|
truncated: false
|
|
3891
3953
|
};
|
|
3892
3954
|
}
|
|
3955
|
+
const cappedOutput = normalizeCommandOutput(json);
|
|
3956
|
+
const truncated = opts.spawnTruncated === true || Buffer.byteLength(json, "utf8") > COMMAND_OUTPUT_MAX_BYTES;
|
|
3893
3957
|
try {
|
|
3894
3958
|
const data = JSON.parse(json);
|
|
3895
|
-
|
|
3896
|
-
const
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
advisories.push({
|
|
3900
|
-
severity: adv.severity ?? "unknown",
|
|
3901
|
-
package: adv.module_name ?? id,
|
|
3902
|
-
title: adv.title ?? "Unknown vulnerability",
|
|
3903
|
-
url: adv.url ?? ""
|
|
3904
|
-
});
|
|
3959
|
+
let advisories = extractAdvisories(data);
|
|
3960
|
+
const minRank = opts.level ? SEVERITY_RANK[opts.level] ?? 0 : 0;
|
|
3961
|
+
if (minRank > 0) {
|
|
3962
|
+
advisories = advisories.filter((a) => (SEVERITY_RANK[a.severity] ?? 0) >= minRank);
|
|
3905
3963
|
}
|
|
3906
3964
|
const total = advisories.length;
|
|
3907
3965
|
const summary = total === 0 ? "No vulnerabilities found" : `Found ${total} vulnerabilities: ${advisories.filter((a) => a.severity === "critical").length} critical, ${advisories.filter((a) => a.severity === "high").length} high`;
|
|
@@ -3910,8 +3968,8 @@ function parseAuditOutput(json, exitCode) {
|
|
|
3910
3968
|
vulnerabilities: advisories,
|
|
3911
3969
|
total,
|
|
3912
3970
|
summary,
|
|
3913
|
-
output:
|
|
3914
|
-
truncated
|
|
3971
|
+
output: cappedOutput,
|
|
3972
|
+
truncated
|
|
3915
3973
|
};
|
|
3916
3974
|
} catch {
|
|
3917
3975
|
return {
|
|
@@ -3919,11 +3977,42 @@ function parseAuditOutput(json, exitCode) {
|
|
|
3919
3977
|
vulnerabilities: [],
|
|
3920
3978
|
total: 0,
|
|
3921
3979
|
summary: "Could not parse audit output",
|
|
3922
|
-
output:
|
|
3923
|
-
truncated
|
|
3980
|
+
output: cappedOutput,
|
|
3981
|
+
truncated
|
|
3924
3982
|
};
|
|
3925
3983
|
}
|
|
3926
3984
|
}
|
|
3985
|
+
function extractAdvisories(data) {
|
|
3986
|
+
const advisories = [];
|
|
3987
|
+
const ads = data["advisories"];
|
|
3988
|
+
if (ads && typeof ads === "object") {
|
|
3989
|
+
for (const [id, value] of Object.entries(ads)) {
|
|
3990
|
+
const adv = value ?? {};
|
|
3991
|
+
advisories.push({
|
|
3992
|
+
severity: typeof adv["severity"] === "string" ? adv["severity"] : "unknown",
|
|
3993
|
+
package: typeof adv["module_name"] === "string" ? adv["module_name"] : id,
|
|
3994
|
+
title: typeof adv["title"] === "string" ? adv["title"] : "Unknown vulnerability",
|
|
3995
|
+
url: typeof adv["url"] === "string" ? adv["url"] : ""
|
|
3996
|
+
});
|
|
3997
|
+
}
|
|
3998
|
+
return advisories;
|
|
3999
|
+
}
|
|
4000
|
+
const vulns = data["vulnerabilities"];
|
|
4001
|
+
if (vulns && typeof vulns === "object") {
|
|
4002
|
+
for (const [pkg, value] of Object.entries(vulns)) {
|
|
4003
|
+
const vuln = value ?? {};
|
|
4004
|
+
const via = Array.isArray(vuln["via"]) ? vuln["via"] : [];
|
|
4005
|
+
const detail = via.find((v) => !!v && typeof v === "object");
|
|
4006
|
+
advisories.push({
|
|
4007
|
+
severity: typeof vuln["severity"] === "string" ? vuln["severity"] : "unknown",
|
|
4008
|
+
package: pkg,
|
|
4009
|
+
title: detail && typeof detail["title"] === "string" ? detail["title"] : "Unknown vulnerability",
|
|
4010
|
+
url: detail && typeof detail["url"] === "string" ? detail["url"] : ""
|
|
4011
|
+
});
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
return advisories;
|
|
4015
|
+
}
|
|
3927
4016
|
export {
|
|
3928
4017
|
auditTool
|
|
3929
4018
|
};
|
package/dist/bash.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/bash.ts
|
|
2
2
|
import { spawn as spawn2 } from "node:child_process";
|
|
3
3
|
import * as os4 from "node:os";
|
|
4
|
+
import { StringDecoder } from "node:string_decoder";
|
|
4
5
|
import {
|
|
5
6
|
emitProcessCompleted,
|
|
6
7
|
emitProcessOutput,
|
|
@@ -394,8 +395,11 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
394
395
|
/--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
|
|
395
396
|
// -t short flag (token): attached (-tVALUE), separated (-t VALUE), or -t=VALUE.
|
|
396
397
|
// (?<![-\w]) anchors to a token start so we don't match the `-t` inside `--token`.
|
|
398
|
+
// The value must be token-like (>= 8 chars) so ordinary combined flags such
|
|
399
|
+
// as `tar -tf` / `ssh -tt` are not eaten. Global flag: EVERY occurrence is
|
|
400
|
+
// redacted, not just the first.
|
|
397
401
|
// NOTE: synced with @wrongstack/core observability/redact-command.ts.
|
|
398
|
-
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]
|
|
402
|
+
/(?<![-\w])-t(?:[=\s]+)?[^\s,-]{8,}/g,
|
|
399
403
|
// -p|-password|-a (redis auth) short flags: attached + separated + =value.
|
|
400
404
|
// Same token-start anchor; over-redaction is an accepted tradeoff for a
|
|
401
405
|
// redaction function. Synced with core copy.
|
|
@@ -403,8 +407,9 @@ var SENSITIVE_FLAG_PATTERNS = [
|
|
|
403
407
|
// env var–style secrets: TOKEN=x, API_KEY=y, etc.
|
|
404
408
|
/(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
|
|
405
409
|
// Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
|
|
406
|
-
// when preceded by a flag name (e.g. --github-token=EyJ...).
|
|
407
|
-
|
|
410
|
+
// when preceded by a flag name (e.g. --github-token=EyJ...). Global flag so
|
|
411
|
+
// every such flag in the command line is redacted, not just the first.
|
|
412
|
+
/--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/g
|
|
408
413
|
];
|
|
409
414
|
function redactCommand(cmd) {
|
|
410
415
|
let result = cmd;
|
|
@@ -493,11 +498,15 @@ var ProcessRegistryImpl = class {
|
|
|
493
498
|
return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;
|
|
494
499
|
}
|
|
495
500
|
_canSignalProcessGroup(p) {
|
|
496
|
-
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
501
|
+
return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && p.child !== null && typeof p.child.pid === "number" && p.child.pid === p.pid;
|
|
497
502
|
}
|
|
498
503
|
_killChildDirect(p, signal) {
|
|
499
504
|
try {
|
|
500
|
-
p.child
|
|
505
|
+
if (p.child) {
|
|
506
|
+
p.child.kill(signal);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (this._isSafeSignalPid(p.pid)) process.kill(p.pid, signal);
|
|
501
510
|
} catch {
|
|
502
511
|
}
|
|
503
512
|
}
|
|
@@ -695,15 +704,15 @@ var ProcessRegistryImpl = class {
|
|
|
695
704
|
this._pruneStale(pid);
|
|
696
705
|
const p = this.processes.get(pid);
|
|
697
706
|
if (!p) return false;
|
|
698
|
-
if (p.killed) return true;
|
|
707
|
+
if (p.killed && opts.force !== true) return true;
|
|
699
708
|
if (p.protected && opts.includeProtected !== true) return false;
|
|
700
709
|
if (opts.preserveBackground && p.background) return false;
|
|
701
710
|
const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
|
|
702
711
|
const isWin2 = os.platform() === "win32";
|
|
703
712
|
if (isWin2) {
|
|
704
|
-
const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
|
|
713
|
+
const liveRealChild = p.child === null || p.child.exitCode === null && typeof p.child.pid === "number";
|
|
705
714
|
const directFallback = () => {
|
|
706
|
-
if (p.child.exitCode === null) {
|
|
715
|
+
if (p.child && p.child.exitCode === null) {
|
|
707
716
|
try {
|
|
708
717
|
p.child.kill("SIGKILL");
|
|
709
718
|
} catch {
|
|
@@ -715,10 +724,7 @@ var ProcessRegistryImpl = class {
|
|
|
715
724
|
onSettled: directFallback
|
|
716
725
|
})) {
|
|
717
726
|
} else {
|
|
718
|
-
|
|
719
|
-
p.child.kill(force ? "SIGKILL" : "SIGTERM");
|
|
720
|
-
} catch {
|
|
721
|
-
}
|
|
727
|
+
this._killChildDirect(p, force ? "SIGKILL" : "SIGTERM");
|
|
722
728
|
}
|
|
723
729
|
p.killed = true;
|
|
724
730
|
return true;
|
|
@@ -729,7 +735,7 @@ var ProcessRegistryImpl = class {
|
|
|
729
735
|
} else {
|
|
730
736
|
this._killPosix(p, "SIGTERM");
|
|
731
737
|
const timer = setTimeout(() => {
|
|
732
|
-
if (this.processes.has(pid) && !p.child
|
|
738
|
+
if (this.processes.has(pid) && !p.child?.killed) {
|
|
733
739
|
this._killPosix(p, "SIGKILL");
|
|
734
740
|
}
|
|
735
741
|
}, graceMs);
|
|
@@ -784,6 +790,16 @@ var ProcessRegistryImpl = class {
|
|
|
784
790
|
* before reusing a PID, but we want to clean up before that becomes a risk.
|
|
785
791
|
*/
|
|
786
792
|
_isStaleEntry(entry) {
|
|
793
|
+
if (entry.child === null) {
|
|
794
|
+
if (Date.now() - entry.startedAt <= 6e4) return false;
|
|
795
|
+
if (os.platform() === "win32") return false;
|
|
796
|
+
try {
|
|
797
|
+
process.kill(entry.pid, 0);
|
|
798
|
+
return false;
|
|
799
|
+
} catch (err) {
|
|
800
|
+
return err.code !== "EPERM";
|
|
801
|
+
}
|
|
802
|
+
}
|
|
787
803
|
return entry.child.exitCode !== null && Date.now() - entry.startedAt > 6e4;
|
|
788
804
|
}
|
|
789
805
|
/**
|
|
@@ -1084,7 +1100,6 @@ var PersistentProcessRegistry = class {
|
|
|
1084
1100
|
try {
|
|
1085
1101
|
const data = await readRegistryFile(this.registryPath);
|
|
1086
1102
|
data.instances.set(String(entry.pid), entry);
|
|
1087
|
-
const child = null;
|
|
1088
1103
|
this.baseRegistry.register({
|
|
1089
1104
|
pid: entry.pid,
|
|
1090
1105
|
name: entry.name,
|
|
@@ -1092,7 +1107,7 @@ var PersistentProcessRegistry = class {
|
|
|
1092
1107
|
startedAt: entry.startedAt,
|
|
1093
1108
|
sessionId: entry.sessionId,
|
|
1094
1109
|
protected: entry.protected,
|
|
1095
|
-
child
|
|
1110
|
+
child: null
|
|
1096
1111
|
});
|
|
1097
1112
|
await writeRegistryFile(this.registryPath, data);
|
|
1098
1113
|
} finally {
|
|
@@ -1652,7 +1667,7 @@ function looksLikePowerShell(command) {
|
|
|
1652
1667
|
return true;
|
|
1653
1668
|
}
|
|
1654
1669
|
if (PS_VERB_RE.test(trimmed)) return true;
|
|
1655
|
-
if (/(?:^|[\s;&|])(gci|gi|gp|gcm|gps
|
|
1670
|
+
if (/(?:^|[\s;&|])(gci|gi|gp|gcm|gps)\b/i.test(trimmed)) {
|
|
1656
1671
|
return true;
|
|
1657
1672
|
}
|
|
1658
1673
|
if (looksLikePowerShellExtended(command)) return true;
|
|
@@ -1782,7 +1797,7 @@ var bashTool = {
|
|
|
1782
1797
|
name: "bash",
|
|
1783
1798
|
category: "Shell",
|
|
1784
1799
|
description: "Execute an arbitrary command in the user's default shell (bash/zsh/pwsh/cmd). stdout and stderr are merged into one stream. This is the most powerful and dangerous tool \u2014 it gives the model full access to the developer's machine. Prefer specialized tools whenever possible.",
|
|
1785
|
-
usageHint: "SECURITY WARNING: This tool runs with the full privileges of the current user.\n\nBest practices for the model:\n- Strongly prefer `exec` for known safe commands (node, npm, pnpm, tsc, git, etc.).\n- Use bash only when you genuinely need shell features (pipes, redirection, complex one-liners).\n- Prefer single focused commands over huge `&&` chains.\n- Use `background: true` only for long-running processes (dev servers, watchers).\n- The working directory is the project root.\n- Output may be truncated in the middle for very large results.",
|
|
1800
|
+
usageHint: "SECURITY WARNING: This tool runs with the full privileges of the current user.\n\nBest practices for the model:\n- Strongly prefer `exec` for known safe commands (node, npm, pnpm, tsc, git, etc.).\n- Use bash only when you genuinely need shell features (pipes, redirection, complex one-liners).\n- Prefer single focused commands over huge `&&` chains.\n- Use `background: true` only for long-running processes (dev servers, watchers).\n- The working directory is the session working dir (changed via `set_working_dir`), defaulting to the project root.\n- Output may be truncated in the middle for very large results.",
|
|
1786
1801
|
selection: {
|
|
1787
1802
|
doNotUseWhen: "the command is allowlisted and does not require pipes, redirection, or shell expansion.",
|
|
1788
1803
|
useInstead: ["exec"]
|
|
@@ -1796,7 +1811,14 @@ var bashTool = {
|
|
|
1796
1811
|
// explicitly removes the implicit cross-tool aliasing.
|
|
1797
1812
|
subjectKey: "command",
|
|
1798
1813
|
capabilities: ["shell.arbitrary"],
|
|
1799
|
-
|
|
1814
|
+
// Executor-level abort ceiling. Must sit ABOVE the per-call `timeout_ms`
|
|
1815
|
+
// ceiling (600_000): the tool's own timer tree-kills and returns a
|
|
1816
|
+
// structured `timed_out: true` result, while the executor's
|
|
1817
|
+
// AbortSignal.timeout is a blunt abort. The old value (300_000) meant any
|
|
1818
|
+
// timeout_ms > 5min was silently cut short by the executor. The 10s margin
|
|
1819
|
+
// covers the kill/teardown window. (The executor additionally clamps to
|
|
1820
|
+
// config `tools.maxToolTimeoutMs`.)
|
|
1821
|
+
timeoutMs: 61e4,
|
|
1800
1822
|
maxOutputBytes: MAX_OUTPUT,
|
|
1801
1823
|
estimatedDurationMs: 3e4,
|
|
1802
1824
|
inputSchema: {
|
|
@@ -1808,7 +1830,7 @@ var bashTool = {
|
|
|
1808
1830
|
},
|
|
1809
1831
|
timeout_ms: {
|
|
1810
1832
|
type: "integer",
|
|
1811
|
-
description: "Optional timeout for this specific command in milliseconds."
|
|
1833
|
+
description: "Optional timeout for this specific command in milliseconds (default 300000, max 600000)."
|
|
1812
1834
|
},
|
|
1813
1835
|
background: {
|
|
1814
1836
|
type: "boolean",
|
|
@@ -1859,16 +1881,7 @@ var bashTool = {
|
|
|
1859
1881
|
return;
|
|
1860
1882
|
}
|
|
1861
1883
|
const PIPE_TO_SHELL_PATTERN = /\|\s*(sh|bash|ksh|zsh|fish|cmd|powershell|pwsh)/i;
|
|
1862
|
-
|
|
1863
|
-
console.warn(JSON.stringify({
|
|
1864
|
-
level: "warn",
|
|
1865
|
-
event: "bash.pipe_to_shell_detected",
|
|
1866
|
-
message: "Detected pipe-to-shell pattern. Consider reviewing the full command before confirming.",
|
|
1867
|
-
command_prefix: input.command.slice(0, 100),
|
|
1868
|
-
// Log first 100 chars for review
|
|
1869
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1870
|
-
}));
|
|
1871
|
-
}
|
|
1884
|
+
const pipeToShellNote = PIPE_TO_SHELL_PATTERN.test(input.command) ? "\n\n[wrongstack] Caution: this command pipes output into a shell interpreter (pipe-to-shell). Piped content executes as arbitrary code \u2014 review the source before trusting the result, and prefer downloading to a file and inspecting it first." : "";
|
|
1872
1885
|
const timeoutMs = Math.max(1, Math.min(input.timeout_ms ?? DEFAULT_TIMEOUT_MS, 6e5));
|
|
1873
1886
|
const isWin2 = os4.platform() === "win32";
|
|
1874
1887
|
let plan;
|
|
@@ -1902,11 +1915,12 @@ var bashTool = {
|
|
|
1902
1915
|
const shell = plan.bin;
|
|
1903
1916
|
const args = plan.useStdin ? [...plan.argv] : [...plan.argv, input.command];
|
|
1904
1917
|
const env = buildChildEnv(ctx.session?.id);
|
|
1918
|
+
const spawnCwd = ctx.workingDir ?? ctx.projectRoot;
|
|
1905
1919
|
const detached = !isWin2;
|
|
1906
1920
|
const startedAt = Date.now();
|
|
1907
1921
|
if (input.background) {
|
|
1908
1922
|
const child2 = spawn2(shell, args, {
|
|
1909
|
-
cwd:
|
|
1923
|
+
cwd: spawnCwd,
|
|
1910
1924
|
env,
|
|
1911
1925
|
// PowerShell takes the script on stdin (no argv quoting); cmd.exe
|
|
1912
1926
|
// and POSIX shells ignore stdin when given the command inline.
|
|
@@ -1937,7 +1951,7 @@ var bashTool = {
|
|
|
1937
1951
|
parentPid: process.pid,
|
|
1938
1952
|
command: redactCommand(`${shell} ${args.join(" ")}`),
|
|
1939
1953
|
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
1940
|
-
cwd:
|
|
1954
|
+
cwd: spawnCwd,
|
|
1941
1955
|
background: true,
|
|
1942
1956
|
startedAt: new Date(startedAt).toISOString()
|
|
1943
1957
|
});
|
|
@@ -1981,7 +1995,9 @@ var bashTool = {
|
|
|
1981
1995
|
yield {
|
|
1982
1996
|
type: "final",
|
|
1983
1997
|
output: {
|
|
1984
|
-
output
|
|
1998
|
+
// Background runs have no captured output; the pipe-to-shell caution
|
|
1999
|
+
// (when present) is the only thing worth surfacing.
|
|
2000
|
+
output: pipeToShellNote.trim(),
|
|
1985
2001
|
exit_code: null,
|
|
1986
2002
|
timed_out: false,
|
|
1987
2003
|
pid: pid2
|
|
@@ -1998,7 +2014,7 @@ var bashTool = {
|
|
|
1998
2014
|
return;
|
|
1999
2015
|
}
|
|
2000
2016
|
const child = spawn2(shell, args, {
|
|
2001
|
-
cwd:
|
|
2017
|
+
cwd: spawnCwd,
|
|
2002
2018
|
env,
|
|
2003
2019
|
// PowerShell takes the script on stdin (no argv quoting); cmd.exe
|
|
2004
2020
|
// and POSIX shells ignore stdin when given the command inline.
|
|
@@ -2023,7 +2039,7 @@ var bashTool = {
|
|
|
2023
2039
|
parentPid: process.pid,
|
|
2024
2040
|
command: redactCommand(`${shell} ${args.join(" ")}`),
|
|
2025
2041
|
args: redactCommand(args.join(" ")).split(" ").filter(Boolean),
|
|
2026
|
-
cwd:
|
|
2042
|
+
cwd: spawnCwd,
|
|
2027
2043
|
background: false,
|
|
2028
2044
|
startedAt: new Date(startedAt).toISOString()
|
|
2029
2045
|
});
|
|
@@ -2142,8 +2158,10 @@ var bashTool = {
|
|
|
2142
2158
|
child.stderr?.resume();
|
|
2143
2159
|
}
|
|
2144
2160
|
};
|
|
2161
|
+
const stdoutDecoder = new StringDecoder("utf8");
|
|
2162
|
+
const stderrDecoder = new StringDecoder("utf8");
|
|
2145
2163
|
const onData = (chunk, stream) => {
|
|
2146
|
-
const text =
|
|
2164
|
+
const text = (stream === "stdout" ? stdoutDecoder : stderrDecoder).write(chunk);
|
|
2147
2165
|
if (stream === "stdout") stdoutBytes += chunk.byteLength;
|
|
2148
2166
|
else stderrBytes += chunk.byteLength;
|
|
2149
2167
|
emitProcessOutput({ pid, stream, chunk });
|
|
@@ -2170,6 +2188,12 @@ var bashTool = {
|
|
|
2170
2188
|
if (typeof pid === "number") registry.unregister(pid);
|
|
2171
2189
|
registry.afterCall(Date.now() - startedAt, code !== 0 && code !== null);
|
|
2172
2190
|
completeForeground(timedOut ? 124 : code ?? (signal ? 1 : 0), signal ?? void 0);
|
|
2191
|
+
const tail = stdoutDecoder.end() + stderrDecoder.end();
|
|
2192
|
+
if (tail) {
|
|
2193
|
+
if (buf.length < MAX_OUTPUT) buf += tail.slice(0, MAX_OUTPUT - buf.length);
|
|
2194
|
+
spool.write(tail);
|
|
2195
|
+
pending += tail;
|
|
2196
|
+
}
|
|
2173
2197
|
push({ kind: "end", code });
|
|
2174
2198
|
});
|
|
2175
2199
|
try {
|
|
@@ -2189,7 +2213,7 @@ var bashTool = {
|
|
|
2189
2213
|
output: {
|
|
2190
2214
|
output: normalizeCommandOutput(buf) + (spooled ? spoolNote(spooled) : "") + (hint ? `
|
|
2191
2215
|
|
|
2192
|
-
${hint}` : ""),
|
|
2216
|
+
${hint}` : "") + pipeToShellNote,
|
|
2193
2217
|
exit_code: c.code,
|
|
2194
2218
|
timed_out: timedOut
|
|
2195
2219
|
}
|
|
@@ -2251,7 +2275,7 @@ ${hint}` : ""),
|
|
|
2251
2275
|
if (!sessionId) return;
|
|
2252
2276
|
for (const entry of registry.bySession(sessionId)) {
|
|
2253
2277
|
if (entry.name !== "bash") continue;
|
|
2254
|
-
if (entry.child.exitCode !== null) continue;
|
|
2278
|
+
if (entry.child && entry.child.exitCode !== null) continue;
|
|
2255
2279
|
if (entry.background) continue;
|
|
2256
2280
|
if (entry.protected) continue;
|
|
2257
2281
|
registry.kill(entry.pid, { force: true });
|