@wrongstack/tools 0.273.0 → 0.273.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/audit.js.map +1 -1
- package/dist/bash.js +193 -14
- package/dist/bash.js.map +1 -1
- package/dist/builtin.js +295 -91
- package/dist/builtin.js.map +1 -1
- package/dist/exec.d.ts +20 -1
- package/dist/exec.js +132 -71
- package/dist/exec.js.map +1 -1
- package/dist/format.js.map +1 -1
- package/dist/index.d.ts +111 -2
- package/dist/index.js +366 -121
- package/dist/index.js.map +1 -1
- package/dist/install.js.map +1 -1
- package/dist/lint.js.map +1 -1
- package/dist/outdated.js.map +1 -1
- package/dist/pack.js +295 -91
- package/dist/pack.js.map +1 -1
- package/dist/test.js.map +1 -1
- package/dist/typecheck.js.map +1 -1
- package/package.json +2 -2
package/dist/exec.d.ts
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { Tool } from '@wrongstack/core';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Apply the configured exec command policy. Recomputes the effective allowlist
|
|
5
|
+
* as `DEFAULT ∪ allow − deny`. Call once at boot from
|
|
6
|
+
* `config.tools.exec.{allow,deny}`. Idempotent (always rebuilt from defaults).
|
|
7
|
+
*
|
|
8
|
+
* SECURITY: `allow` must originate from TRUSTED config only — the config loader
|
|
9
|
+
* strips `tools.exec.allow` from the untrusted in-project repo config before it
|
|
10
|
+
* reaches here. `deny` is safe from any source (it only narrows).
|
|
11
|
+
*/
|
|
12
|
+
declare function configureExecPolicy(opts?: {
|
|
13
|
+
allow?: readonly string[] | undefined;
|
|
14
|
+
deny?: readonly string[] | undefined;
|
|
15
|
+
}): void;
|
|
16
|
+
/** Reset the exec allowlist to the built-in defaults (tests / re-init). */
|
|
17
|
+
declare function resetExecPolicy(): void;
|
|
18
|
+
/** Whether `cmd` is currently in the effective exec allowlist. */
|
|
19
|
+
declare function isExecCommandAllowed(cmd: string): boolean;
|
|
20
|
+
/** Snapshot of the effective allowlist (sorted) — for tests / diagnostics. */
|
|
21
|
+
declare function getExecAllowlist(): string[];
|
|
3
22
|
interface ExecInput {
|
|
4
23
|
command: string;
|
|
5
24
|
args?: string[] | undefined;
|
|
@@ -17,4 +36,4 @@ interface ExecOutput {
|
|
|
17
36
|
}
|
|
18
37
|
declare const execTool: Tool<ExecInput, ExecOutput>;
|
|
19
38
|
|
|
20
|
-
export { execTool };
|
|
39
|
+
export { configureExecPolicy, execTool, getExecAllowlist, isExecCommandAllowed, resetExecPolicy };
|
package/dist/exec.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { toErrorMessage } from '@wrongstack/core/utils/error';
|
|
2
3
|
import * as Core from '@wrongstack/core';
|
|
3
4
|
import { buildChildEnv, expectDefined, wstackGlobalRoot } from '@wrongstack/core';
|
|
4
5
|
import * as fs from 'node:fs';
|
|
@@ -783,49 +784,86 @@ function assertSafeWin32ShellArgs(args) {
|
|
|
783
784
|
|
|
784
785
|
// src/exec.ts
|
|
785
786
|
var isWin = process.platform === "win32";
|
|
786
|
-
var
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
787
|
+
var DEFAULT_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
|
|
788
|
+
// JS / TS toolchain
|
|
789
|
+
"node",
|
|
790
|
+
"npm",
|
|
791
|
+
"pnpm",
|
|
792
|
+
"yarn",
|
|
793
|
+
"npx",
|
|
794
|
+
"bun",
|
|
795
|
+
"deno",
|
|
796
|
+
"tsc",
|
|
797
|
+
"vitest",
|
|
798
|
+
"jest",
|
|
799
|
+
"biome",
|
|
800
|
+
"eslint",
|
|
801
|
+
"prettier",
|
|
802
|
+
// version control
|
|
803
|
+
"git",
|
|
804
|
+
// Rust
|
|
805
|
+
"cargo",
|
|
806
|
+
"rustc",
|
|
807
|
+
// Go
|
|
808
|
+
"go",
|
|
809
|
+
// Python
|
|
810
|
+
"python",
|
|
811
|
+
"python3",
|
|
812
|
+
"pip",
|
|
813
|
+
"pip3",
|
|
814
|
+
// Ruby
|
|
815
|
+
"ruby",
|
|
816
|
+
"gem",
|
|
817
|
+
"bundle",
|
|
818
|
+
// JVM
|
|
819
|
+
"java",
|
|
820
|
+
"javac",
|
|
821
|
+
"mvn",
|
|
822
|
+
"gradle",
|
|
823
|
+
"gradlew",
|
|
824
|
+
// .NET
|
|
825
|
+
"dotnet",
|
|
826
|
+
// C / C++ / native build
|
|
827
|
+
"make",
|
|
828
|
+
"cmake",
|
|
829
|
+
// containers / orchestration (read-only subcommands; see BLOCKED_ARG_PATTERNS)
|
|
830
|
+
"docker",
|
|
831
|
+
"kubectl",
|
|
832
|
+
// common POSIX file/text utilities
|
|
833
|
+
"ls",
|
|
834
|
+
"cat",
|
|
835
|
+
"head",
|
|
836
|
+
"tail",
|
|
837
|
+
"wc",
|
|
838
|
+
"grep",
|
|
839
|
+
"find",
|
|
840
|
+
"echo",
|
|
841
|
+
"mkdir",
|
|
842
|
+
"cp",
|
|
843
|
+
"mv",
|
|
844
|
+
"rm",
|
|
845
|
+
"touch"
|
|
846
|
+
]);
|
|
847
|
+
var allowedCommands = new Set(DEFAULT_ALLOWED_COMMANDS);
|
|
848
|
+
var normalizeCmd = (c) => c.trim();
|
|
849
|
+
function configureExecPolicy(opts = {}) {
|
|
850
|
+
const next = new Set(DEFAULT_ALLOWED_COMMANDS);
|
|
851
|
+
for (const c of opts.allow ?? []) {
|
|
852
|
+
const n = normalizeCmd(c);
|
|
853
|
+
if (n) next.add(n);
|
|
854
|
+
}
|
|
855
|
+
for (const c of opts.deny ?? []) next.delete(normalizeCmd(c));
|
|
856
|
+
allowedCommands = next;
|
|
857
|
+
}
|
|
858
|
+
function resetExecPolicy() {
|
|
859
|
+
allowedCommands = new Set(DEFAULT_ALLOWED_COMMANDS);
|
|
860
|
+
}
|
|
861
|
+
function isExecCommandAllowed(cmd) {
|
|
862
|
+
return allowedCommands.has(normalizeCmd(cmd));
|
|
863
|
+
}
|
|
864
|
+
function getExecAllowlist() {
|
|
865
|
+
return [...allowedCommands].sort();
|
|
866
|
+
}
|
|
829
867
|
var MAX_ARGS = 20;
|
|
830
868
|
var MAX_OUTPUT = 2e5;
|
|
831
869
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
@@ -887,7 +925,7 @@ var execTool = {
|
|
|
887
925
|
name: "exec",
|
|
888
926
|
category: "Shell",
|
|
889
927
|
description: "Execute a **whitelisted, restricted set of commands** with strict argument validation. This is the **preferred and safer** alternative to the `bash` tool for running development tools (node, npm, pnpm, tsc, git, tests, linters, etc.). It prevents arbitrary command injection and limits what the model can do.",
|
|
890
|
-
usageHint: "PREFERRED SHELL TOOL for most cases.\n\nUse this instead of `bash` whenever possible.\n- `command` must be
|
|
928
|
+
usageHint: "PREFERRED SHELL TOOL for most cases.\n\nUse this instead of `bash` whenever possible.\n- `command` must be in the allowlist. Defaults cover JS (node/npm/pnpm/yarn/bun/deno/tsc/vitest/eslint/biome), Go (`go build`/`go test`), Rust (cargo), Python (python/pip), Ruby (gem/bundle), JVM (java/mvn/gradle), .NET (dotnet), native (make/cmake), and git. Users can extend it via `tools.exec.allow` in config.\n- Arguments are passed as a clean array (no shell interpretation).\n- `cwd` is validated to stay inside the project.\n- If a command is not allowlisted, the error explains how to add it; for one-off arbitrary commands, fall back to `bash` (with strong justification).\nThis tool significantly reduces the risk compared to full shell access.",
|
|
891
929
|
permission: "confirm",
|
|
892
930
|
mutating: true,
|
|
893
931
|
riskTier: "standard",
|
|
@@ -941,12 +979,12 @@ var execTool = {
|
|
|
941
979
|
truncated: false,
|
|
942
980
|
allowed: false
|
|
943
981
|
};
|
|
944
|
-
if (!(cmd
|
|
982
|
+
if (!isExecCommandAllowed(cmd)) {
|
|
945
983
|
return {
|
|
946
984
|
command: cmd,
|
|
947
985
|
args: input.args ?? [],
|
|
948
986
|
stdout: "",
|
|
949
|
-
stderr: `Command "${cmd}" not in allowlist.
|
|
987
|
+
stderr: `Command "${cmd}" not in allowlist. Add it to your ~/.wrongstack/config.json under "tools": { "exec": { "allow": ["${cmd}"] } }, or use the bash tool for one-off arbitrary commands.`,
|
|
950
988
|
exitCode: 1,
|
|
951
989
|
truncated: false,
|
|
952
990
|
allowed: false
|
|
@@ -989,19 +1027,58 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
989
1027
|
let stdout = "";
|
|
990
1028
|
let stderr = "";
|
|
991
1029
|
let killed = false;
|
|
1030
|
+
const resolvedOnce = { value: false };
|
|
1031
|
+
const finish = (result) => {
|
|
1032
|
+
if (resolvedOnce.value) return;
|
|
1033
|
+
resolvedOnce.value = true;
|
|
1034
|
+
resolve2(result);
|
|
1035
|
+
};
|
|
992
1036
|
const startedAt = Date.now();
|
|
993
1037
|
const spool = createOutputSpool({ tool: `exec-${cmd}`, thresholdBytes: MAX_OUTPUT });
|
|
994
1038
|
const resolved = resolveWin32Command(cmd);
|
|
995
1039
|
const needsShell = isWin && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
|
|
996
1040
|
const spawnCmd = needsShell ? cmd : resolved;
|
|
997
1041
|
if (needsShell) assertSafeWin32ShellArgs(args);
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1042
|
+
let child;
|
|
1043
|
+
try {
|
|
1044
|
+
child = spawn(spawnCmd, args, {
|
|
1045
|
+
cwd,
|
|
1046
|
+
env: buildChildEnv(sessionId),
|
|
1047
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1048
|
+
windowsHide: true,
|
|
1049
|
+
...isWin ? {} : { signal },
|
|
1050
|
+
...needsShell ? { shell: true, windowsVerbatimArguments: true } : {}
|
|
1051
|
+
});
|
|
1052
|
+
} catch (err) {
|
|
1053
|
+
spool.finalize();
|
|
1054
|
+
finish({
|
|
1055
|
+
command: cmd,
|
|
1056
|
+
args,
|
|
1057
|
+
stdout: "",
|
|
1058
|
+
stderr: `spawn failed: ${toErrorMessage(err)}`,
|
|
1059
|
+
exitCode: 1,
|
|
1060
|
+
truncated: false,
|
|
1061
|
+
allowed: true
|
|
1062
|
+
});
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
child.on("error", (err) => {
|
|
1066
|
+
const isAbort = err && err.code === "ABORT_ERR";
|
|
1067
|
+
const stderrText = isAbort ? `Aborted: ${err.message}` : err.message;
|
|
1068
|
+
clearTimeout(timer);
|
|
1069
|
+
if (isWin) signal.removeEventListener("abort", onAbort);
|
|
1070
|
+
if (typeof pid === "number") registry.unregister(pid);
|
|
1071
|
+
registry.afterCall(Date.now() - startedAt, true);
|
|
1072
|
+
spool.finalize();
|
|
1073
|
+
finish({
|
|
1074
|
+
command: cmd,
|
|
1075
|
+
args,
|
|
1076
|
+
stdout: normalizeCommandOutput(stdout),
|
|
1077
|
+
stderr: stderrText,
|
|
1078
|
+
exitCode: isAbort ? 124 : 1,
|
|
1079
|
+
truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES,
|
|
1080
|
+
allowed: true
|
|
1081
|
+
});
|
|
1005
1082
|
});
|
|
1006
1083
|
const registry = getProcessRegistry();
|
|
1007
1084
|
const pid = child.pid;
|
|
@@ -1041,7 +1118,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
1041
1118
|
const exitCode = killed ? 124 : code ?? 1;
|
|
1042
1119
|
registry.afterCall(durationMs, exitCode !== 0);
|
|
1043
1120
|
const spooled = spool.finalize();
|
|
1044
|
-
|
|
1121
|
+
finish({
|
|
1045
1122
|
command: cmd,
|
|
1046
1123
|
args,
|
|
1047
1124
|
stdout: normalizeCommandOutput(stdout) + (spooled ? spoolNote(spooled) : ""),
|
|
@@ -1051,25 +1128,9 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
1051
1128
|
allowed: true
|
|
1052
1129
|
});
|
|
1053
1130
|
});
|
|
1054
|
-
child.on("error", (err) => {
|
|
1055
|
-
clearTimeout(timer);
|
|
1056
|
-
if (isWin) signal.removeEventListener("abort", onAbort);
|
|
1057
|
-
if (typeof pid === "number") registry.unregister(pid);
|
|
1058
|
-
registry.afterCall(Date.now() - startedAt, true);
|
|
1059
|
-
spool.finalize();
|
|
1060
|
-
resolve2({
|
|
1061
|
-
command: cmd,
|
|
1062
|
-
args,
|
|
1063
|
-
stdout: normalizeCommandOutput(stdout),
|
|
1064
|
-
stderr: err.message,
|
|
1065
|
-
exitCode: 1,
|
|
1066
|
-
truncated: Buffer.byteLength(stdout, "utf8") > COMMAND_OUTPUT_MAX_BYTES,
|
|
1067
|
-
allowed: true
|
|
1068
|
-
});
|
|
1069
|
-
});
|
|
1070
1131
|
});
|
|
1071
1132
|
}
|
|
1072
1133
|
|
|
1073
|
-
export { execTool };
|
|
1134
|
+
export { configureExecPolicy, execTool, getExecAllowlist, isExecCommandAllowed, resetExecPolicy };
|
|
1074
1135
|
//# sourceMappingURL=exec.js.map
|
|
1075
1136
|
//# sourceMappingURL=exec.js.map
|