@wrongstack/tools 0.3.3 → 0.3.7
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/bash.js +33 -3
- package/dist/bash.js.map +1 -1
- package/dist/builtin.js +151 -46
- package/dist/builtin.js.map +1 -1
- package/dist/exec.js +37 -1
- package/dist/exec.js.map +1 -1
- package/dist/index.js +151 -46
- package/dist/index.js.map +1 -1
- package/dist/install.js +16 -0
- package/dist/install.js.map +1 -1
- package/dist/logs.js +9 -0
- package/dist/logs.js.map +1 -1
- package/dist/pack.js +151 -46
- package/dist/pack.js.map +1 -1
- package/dist/patch.js +2 -1
- package/dist/patch.js.map +1 -1
- package/dist/read.js +8 -1
- package/dist/read.js.map +1 -1
- package/dist/scaffold.js +10 -3
- package/dist/scaffold.js.map +1 -1
- package/dist/write.js +4 -4
- package/dist/write.js.map +1 -1
- package/package.json +2 -2
package/dist/exec.js
CHANGED
|
@@ -43,7 +43,7 @@ var ALLOWED_COMMANDS = {
|
|
|
43
43
|
cargo: ["--version", "build", "test", "check"],
|
|
44
44
|
rustc: ["--version"],
|
|
45
45
|
go: ["version", "run", "build", "test"],
|
|
46
|
-
python: ["--version"
|
|
46
|
+
python: ["--version"],
|
|
47
47
|
pip: ["--version", "install", "list"],
|
|
48
48
|
docker: ["--version", "ps", "images", "build"],
|
|
49
49
|
kubectl: ["version", "get", "describe", "logs"]
|
|
@@ -51,6 +51,30 @@ var ALLOWED_COMMANDS = {
|
|
|
51
51
|
var MAX_ARGS = 20;
|
|
52
52
|
var MAX_OUTPUT = 2e5;
|
|
53
53
|
var TIMEOUT_MS = 3e4;
|
|
54
|
+
var BLOCKED_ARG_PATTERNS = {
|
|
55
|
+
// python -c/--command executes arbitrary code; python -m runs modules
|
|
56
|
+
python: [/-c$/, /^--command$/, /^-m$/, /^--module$/],
|
|
57
|
+
// git --exec=<cmd> runs arbitrary commands via upload-pack/receive-pack
|
|
58
|
+
git: [/^--exec=/, /^--upload-pack=/, /^--receive-pack=/],
|
|
59
|
+
// node -r/--require preloads arbitrary modules; --eval executes code
|
|
60
|
+
node: [/^-r$/, /^--require$/, /^-e$/, /^--eval$/, /^--prof-process$/],
|
|
61
|
+
// go run could execute arbitrary .go files; -ldflags could inject build-time code
|
|
62
|
+
go: [/^-ldflags$/],
|
|
63
|
+
// bun --preload is similar to node --require
|
|
64
|
+
bun: [/^--preload$/]
|
|
65
|
+
};
|
|
66
|
+
function validateArgs(cmd, args) {
|
|
67
|
+
const blocked = BLOCKED_ARG_PATTERNS[cmd];
|
|
68
|
+
if (!blocked) return null;
|
|
69
|
+
for (const arg of args) {
|
|
70
|
+
for (const pattern of blocked) {
|
|
71
|
+
if (pattern.test(arg)) {
|
|
72
|
+
return `Blocked argument "${arg}" for command "${cmd}" (matches security pattern ${pattern})`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
54
78
|
var execTool = {
|
|
55
79
|
name: "exec",
|
|
56
80
|
category: "Shell",
|
|
@@ -94,6 +118,18 @@ var execTool = {
|
|
|
94
118
|
}
|
|
95
119
|
const args = (input.args ?? []).slice(0, MAX_ARGS);
|
|
96
120
|
const timeout = Math.max(1, Math.min(input.timeout ?? TIMEOUT_MS, TIMEOUT_MS));
|
|
121
|
+
const argError = validateArgs(cmd, args);
|
|
122
|
+
if (argError) {
|
|
123
|
+
return {
|
|
124
|
+
command: cmd,
|
|
125
|
+
args,
|
|
126
|
+
stdout: "",
|
|
127
|
+
stderr: argError,
|
|
128
|
+
exitCode: 1,
|
|
129
|
+
truncated: false,
|
|
130
|
+
allowed: false
|
|
131
|
+
};
|
|
132
|
+
}
|
|
97
133
|
const requestedCwd = input.cwd ? path.resolve(ctx.projectRoot, input.cwd) : ctx.cwd;
|
|
98
134
|
const rel = path.relative(ctx.projectRoot, requestedCwd);
|
|
99
135
|
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
package/dist/exec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/exec.ts"],"names":["resolve"],"mappings":"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","file":"exec.js","sourcesContent":["import { spawn } from 'node:child_process';\nimport * as path from 'node:path';\nimport type { Tool } from '@wrongstack/core';\nimport { buildChildEnv } from './_env.js';\n\nconst ALLOWED_COMMANDS: Record<string, string[]> = {\n node: ['--version', '-r', '--input-type=module'],\n npm: ['--version', 'init', 'install', 'test', 'list', 'pkg', 'doctor'],\n pnpm: ['--version', 'init', 'install', 'add', 'remove', 'list'],\n npx: ['--version'],\n git: [\n '--version',\n 'status',\n 'log',\n 'diff',\n 'branch',\n 'checkout',\n 'stash',\n 'add',\n 'commit',\n 'push',\n 'pull',\n ],\n ls: ['-la', '-l', '-a'],\n cat: [],\n head: ['-n'],\n tail: ['-n'],\n wc: ['-l', '-w', '-c'],\n grep: [],\n find: [],\n echo: [],\n mkdir: ['-p'],\n cp: ['-r'],\n mv: [],\n rm: ['-rf'],\n touch: [],\n bun: ['--version', 'add', 'init'],\n tsc: ['--version', '--noEmit', '--project'],\n vitest: ['--version', 'run', '--coverage'],\n biome: ['--version', 'lint', 'format', 'check'],\n cargo: ['--version', 'build', 'test', 'check'],\n rustc: ['--version'],\n go: ['version', 'run', 'build', 'test'],\n python: ['--version', '-c'],\n pip: ['--version', 'install', 'list'],\n docker: ['--version', 'ps', 'images', 'build'],\n kubectl: ['version', 'get', 'describe', 'logs'],\n};\n\nconst MAX_ARGS = 20;\nconst MAX_OUTPUT = 200_000;\nconst TIMEOUT_MS = 30_000;\n\ninterface ExecInput {\n command: string;\n args?: string[];\n cwd?: string;\n timeout?: number;\n}\n\ninterface ExecOutput {\n command: string;\n args: string[];\n stdout: string;\n stderr: string;\n exitCode: number;\n truncated: boolean;\n allowed: boolean;\n}\n\nexport const execTool: Tool<ExecInput, ExecOutput> = {\n name: 'exec',\n category: 'Shell',\n description:\n 'Restricted shell that only runs pre-approved commands with constrained arguments. Safer alternative to `bash`.',\n usageHint:\n 'Set `command` (must be in allowlist). `args` passed through. For arbitrary shell access use the `bash` tool instead.',\n permission: 'confirm',\n mutating: true,\n timeoutMs: TIMEOUT_MS,\n inputSchema: {\n type: 'object',\n properties: {\n command: { type: 'string', description: 'Command to run (must be in allowlist)' },\n args: { type: 'array', items: { type: 'string' }, description: 'Arguments' },\n cwd: { type: 'string', description: 'Working directory (must resolve inside project root)' },\n timeout: { type: 'integer', description: 'Timeout in ms (default: 30000)' },\n },\n required: ['command'],\n },\n async execute(input, ctx, opts) {\n const cmd = input.command.trim();\n if (!cmd)\n return {\n command: cmd,\n args: [],\n stdout: '',\n stderr: 'Empty command',\n exitCode: 1,\n truncated: false,\n allowed: false,\n };\n\n if (!(cmd in ALLOWED_COMMANDS)) {\n return {\n command: cmd,\n args: input.args ?? [],\n stdout: '',\n stderr: `Command \"${cmd}\" not in allowlist. Use the bash tool for arbitrary commands.`,\n exitCode: 1,\n truncated: false,\n allowed: false,\n };\n }\n\n const args = (input.args ?? []).slice(0, MAX_ARGS);\n const timeout = Math.max(1, Math.min(input.timeout ?? TIMEOUT_MS, TIMEOUT_MS));\n\n // Resolve cwd inside the project root. Model-supplied paths like '/etc'\n // would otherwise let allowlisted commands operate anywhere on disk.\n const requestedCwd = input.cwd ? path.resolve(ctx.projectRoot, input.cwd) : ctx.cwd;\n const rel = path.relative(ctx.projectRoot, requestedCwd);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n return {\n command: cmd,\n args,\n stdout: '',\n stderr: `cwd \"${input.cwd}\" resolves outside project root`,\n exitCode: 1,\n truncated: false,\n allowed: false,\n };\n }\n const cwd = requestedCwd;\n const signal = opts.signal;\n\n return runCommand(cmd, args, cwd, timeout, signal, ctx.session?.id);\n },\n};\n\nfunction runCommand(\n cmd: string,\n args: string[],\n cwd: string,\n timeout: number,\n signal: AbortSignal,\n sessionId: string | undefined,\n): Promise<ExecOutput> {\n return new Promise((resolve) => {\n let stdout = '';\n let stderr = '';\n let killed = false;\n\n const child = spawn(cmd, args, {\n cwd,\n signal,\n env: buildChildEnv(sessionId),\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n const timer = setTimeout(() => {\n killed = true;\n child.kill('SIGTERM');\n }, timeout);\n\n child.stdout?.on('data', (chunk: Buffer) => {\n if (stdout.length < MAX_OUTPUT) stdout += chunk.toString();\n });\n\n child.stderr?.on('data', (chunk: Buffer) => {\n if (stderr.length < MAX_OUTPUT) stderr += chunk.toString();\n });\n\n child.on('close', (code) => {\n clearTimeout(timer);\n resolve({\n command: cmd,\n args,\n stdout: stdout.slice(0, MAX_OUTPUT),\n stderr: stderr.slice(0, MAX_OUTPUT),\n exitCode: killed ? 124 : (code ?? 1),\n truncated: stdout.length >= MAX_OUTPUT || stderr.length >= MAX_OUTPUT,\n allowed: true,\n });\n });\n\n child.on('error', (err) => {\n clearTimeout(timer);\n resolve({\n command: cmd,\n args,\n stdout: stdout.slice(0, MAX_OUTPUT),\n stderr: err.message,\n exitCode: 1,\n truncated: false,\n allowed: true,\n });\n });\n });\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/exec.ts"],"names":["resolve"],"mappings":"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","file":"exec.js","sourcesContent":["import { spawn } from 'node:child_process';\r\nimport * as path from 'node:path';\r\nimport type { Tool } from '@wrongstack/core';\r\nimport { buildChildEnv } from './_env.js';\r\n\r\nconst ALLOWED_COMMANDS: Record<string, string[]> = {\r\n node: ['--version', '-r', '--input-type=module'],\r\n npm: ['--version', 'init', 'install', 'test', 'list', 'pkg', 'doctor'],\r\n pnpm: ['--version', 'init', 'install', 'add', 'remove', 'list'],\r\n npx: ['--version'],\r\n git: [\r\n '--version',\r\n 'status',\r\n 'log',\r\n 'diff',\r\n 'branch',\r\n 'checkout',\r\n 'stash',\r\n 'add',\r\n 'commit',\r\n 'push',\r\n 'pull',\r\n ],\r\n ls: ['-la', '-l', '-a'],\r\n cat: [],\r\n head: ['-n'],\r\n tail: ['-n'],\r\n wc: ['-l', '-w', '-c'],\r\n grep: [],\r\n find: [],\r\n echo: [],\r\n mkdir: ['-p'],\r\n cp: ['-r'],\r\n mv: [],\r\n rm: ['-rf'],\r\n touch: [],\r\n bun: ['--version', 'add', 'init'],\r\n tsc: ['--version', '--noEmit', '--project'],\r\n vitest: ['--version', 'run', '--coverage'],\r\n biome: ['--version', 'lint', 'format', 'check'],\r\n cargo: ['--version', 'build', 'test', 'check'],\r\n rustc: ['--version'],\r\n go: ['version', 'run', 'build', 'test'],\r\n python: ['--version'],\r\n pip: ['--version', 'install', 'list'],\r\n docker: ['--version', 'ps', 'images', 'build'],\r\n kubectl: ['version', 'get', 'describe', 'logs'],\r\n};\r\n\r\nconst MAX_ARGS = 20;\r\nconst MAX_OUTPUT = 200_000;\r\nconst TIMEOUT_MS = 30_000;\r\n\r\n// Per-command argument validation. Each entry is a list of regex patterns\r\n// that, if matched against any argument, will reject the invocation.\r\n// This blocks common injection vectors through allowlisted commands.\r\nconst BLOCKED_ARG_PATTERNS: Record<string, RegExp[]> = {\r\n // python -c/--command executes arbitrary code; python -m runs modules\r\n python: [/-c$/, /^--command$/, /^-m$/, /^--module$/],\r\n // git --exec=<cmd> runs arbitrary commands via upload-pack/receive-pack\r\n git: [/^--exec=/, /^--upload-pack=/, /^--receive-pack=/],\r\n // node -r/--require preloads arbitrary modules; --eval executes code\r\n node: [/^-r$/, /^--require$/, /^-e$/, /^--eval$/, /^--prof-process$/],\r\n // go run could execute arbitrary .go files; -ldflags could inject build-time code\r\n go: [/^-ldflags$/],\r\n // bun --preload is similar to node --require\r\n bun: [/^--preload$/],\r\n};\r\n\r\nfunction validateArgs(cmd: string, args: string[]): string | null {\r\n const blocked = BLOCKED_ARG_PATTERNS[cmd];\r\n if (!blocked) return null;\r\n\r\n for (const arg of args) {\r\n for (const pattern of blocked) {\r\n if (pattern.test(arg)) {\r\n return `Blocked argument \"${arg}\" for command \"${cmd}\" (matches security pattern ${pattern})`;\r\n }\r\n }\r\n }\r\n return null;\r\n}\r\n\r\ninterface ExecInput {\r\n command: string;\r\n args?: string[];\r\n cwd?: string;\r\n timeout?: number;\r\n}\r\n\r\ninterface ExecOutput {\r\n command: string;\r\n args: string[];\r\n stdout: string;\r\n stderr: string;\r\n exitCode: number;\r\n truncated: boolean;\r\n allowed: boolean;\r\n}\r\n\r\nexport const execTool: Tool<ExecInput, ExecOutput> = {\r\n name: 'exec',\r\n category: 'Shell',\r\n description:\r\n 'Restricted shell that only runs pre-approved commands with constrained arguments. Safer alternative to `bash`.',\r\n usageHint:\r\n 'Set `command` (must be in allowlist). `args` passed through. For arbitrary shell access use the `bash` tool instead.',\r\n permission: 'confirm',\r\n mutating: true,\r\n timeoutMs: TIMEOUT_MS,\r\n inputSchema: {\r\n type: 'object',\r\n properties: {\r\n command: { type: 'string', description: 'Command to run (must be in allowlist)' },\r\n args: { type: 'array', items: { type: 'string' }, description: 'Arguments' },\r\n cwd: { type: 'string', description: 'Working directory (must resolve inside project root)' },\r\n timeout: { type: 'integer', description: 'Timeout in ms (default: 30000)' },\r\n },\r\n required: ['command'],\r\n },\r\n async execute(input, ctx, opts) {\r\n const cmd = input.command.trim();\r\n if (!cmd)\r\n return {\r\n command: cmd,\r\n args: [],\r\n stdout: '',\r\n stderr: 'Empty command',\r\n exitCode: 1,\r\n truncated: false,\r\n allowed: false,\r\n };\r\n\r\n if (!(cmd in ALLOWED_COMMANDS)) {\r\n return {\r\n command: cmd,\r\n args: input.args ?? [],\r\n stdout: '',\r\n stderr: `Command \"${cmd}\" not in allowlist. Use the bash tool for arbitrary commands.`,\r\n exitCode: 1,\r\n truncated: false,\r\n allowed: false,\r\n };\r\n }\r\n\r\n const args = (input.args ?? []).slice(0, MAX_ARGS);\r\n const timeout = Math.max(1, Math.min(input.timeout ?? TIMEOUT_MS, TIMEOUT_MS));\r\n\r\n // Validate args against per-command security patterns\r\n const argError = validateArgs(cmd, args);\r\n if (argError) {\r\n return {\r\n command: cmd,\r\n args,\r\n stdout: '',\r\n stderr: argError,\r\n exitCode: 1,\r\n truncated: false,\r\n allowed: false,\r\n };\r\n }\r\n\r\n // Resolve cwd inside the project root. Model-supplied paths like '/etc'\r\n // would otherwise let allowlisted commands operate anywhere on disk.\r\n const requestedCwd = input.cwd ? path.resolve(ctx.projectRoot, input.cwd) : ctx.cwd;\r\n const rel = path.relative(ctx.projectRoot, requestedCwd);\r\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\r\n return {\r\n command: cmd,\r\n args,\r\n stdout: '',\r\n stderr: `cwd \"${input.cwd}\" resolves outside project root`,\r\n exitCode: 1,\r\n truncated: false,\r\n allowed: false,\r\n };\r\n }\r\n const cwd = requestedCwd;\r\n const signal = opts.signal;\r\n\r\n return runCommand(cmd, args, cwd, timeout, signal, ctx.session?.id);\r\n },\r\n};\r\n\r\nfunction runCommand(\r\n cmd: string,\r\n args: string[],\r\n cwd: string,\r\n timeout: number,\r\n signal: AbortSignal,\r\n sessionId: string | undefined,\r\n): Promise<ExecOutput> {\r\n return new Promise((resolve) => {\r\n let stdout = '';\r\n let stderr = '';\r\n let killed = false;\r\n\r\n const child = spawn(cmd, args, {\r\n cwd,\r\n signal,\r\n env: buildChildEnv(sessionId),\r\n stdio: ['ignore', 'pipe', 'pipe'],\r\n });\r\n const timer = setTimeout(() => {\r\n killed = true;\r\n child.kill('SIGTERM');\r\n }, timeout);\r\n\r\n child.stdout?.on('data', (chunk: Buffer) => {\r\n if (stdout.length < MAX_OUTPUT) stdout += chunk.toString();\r\n });\r\n\r\n child.stderr?.on('data', (chunk: Buffer) => {\r\n if (stderr.length < MAX_OUTPUT) stderr += chunk.toString();\r\n });\r\n\r\n child.on('close', (code) => {\r\n clearTimeout(timer);\r\n resolve({\r\n command: cmd,\r\n args,\r\n stdout: stdout.slice(0, MAX_OUTPUT),\r\n stderr: stderr.slice(0, MAX_OUTPUT),\r\n exitCode: killed ? 124 : (code ?? 1),\r\n truncated: stdout.length >= MAX_OUTPUT || stderr.length >= MAX_OUTPUT,\r\n allowed: true,\r\n });\r\n });\r\n\r\n child.on('error', (err) => {\r\n clearTimeout(timer);\r\n resolve({\r\n command: cmd,\r\n args,\r\n stdout: stdout.slice(0, MAX_OUTPUT),\r\n stderr: err.message,\r\n exitCode: 1,\r\n truncated: false,\r\n allowed: true,\r\n });\r\n });\r\n });\r\n}\r\n"]}
|
package/dist/index.js
CHANGED
|
@@ -67,7 +67,14 @@ var readTool = {
|
|
|
67
67
|
async execute(input, ctx) {
|
|
68
68
|
if (!input?.path) throw new Error("read: path is required");
|
|
69
69
|
const absPath = safeResolve(input.path, ctx);
|
|
70
|
-
|
|
70
|
+
let stat9;
|
|
71
|
+
try {
|
|
72
|
+
stat9 = await fs4.stat(absPath);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
const code = err.code;
|
|
75
|
+
if (code === "ENOENT") throw new Error(`read: file not found "${input.path}"`);
|
|
76
|
+
throw new Error(`read: failed to stat "${input.path}": ${err instanceof Error ? err.message : String(err)}`);
|
|
77
|
+
}
|
|
71
78
|
if (!stat9.isFile()) throw new Error(`read: "${input.path}" is not a regular file`);
|
|
72
79
|
if (stat9.size > MAX_BYTES) {
|
|
73
80
|
throw new Error(`read: file too large (${stat9.size} bytes, limit ${MAX_BYTES})`);
|
|
@@ -125,11 +132,11 @@ var writeTool = {
|
|
|
125
132
|
existed = stat10.isFile();
|
|
126
133
|
if (existed) {
|
|
127
134
|
if (!ctx.hasRead(absPath)) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
135
|
+
prev = await fs4.readFile(absPath, "utf8");
|
|
136
|
+
ctx.recordRead(absPath, stat10.mtimeMs);
|
|
137
|
+
} else {
|
|
138
|
+
prev = await fs4.readFile(absPath, "utf8");
|
|
131
139
|
}
|
|
132
|
-
prev = await fs4.readFile(absPath, "utf8");
|
|
133
140
|
}
|
|
134
141
|
} catch (err) {
|
|
135
142
|
if (err.code !== "ENOENT") {
|
|
@@ -436,13 +443,13 @@ async function globFiles(pattern, base, extraGlob) {
|
|
|
436
443
|
return await globNative(pattern, base, extraGlob);
|
|
437
444
|
}
|
|
438
445
|
function checkRg() {
|
|
439
|
-
return new Promise((
|
|
446
|
+
return new Promise((resolve5) => {
|
|
440
447
|
try {
|
|
441
448
|
const p = spawn("rg", ["--version"], { stdio: "ignore" });
|
|
442
|
-
p.on("error", () =>
|
|
443
|
-
p.on("close", (code) =>
|
|
449
|
+
p.on("error", () => resolve5(false));
|
|
450
|
+
p.on("close", (code) => resolve5(code === 0));
|
|
444
451
|
} catch {
|
|
445
|
-
|
|
452
|
+
resolve5(false);
|
|
446
453
|
}
|
|
447
454
|
});
|
|
448
455
|
}
|
|
@@ -454,10 +461,10 @@ function spawnRgFind(pattern, base) {
|
|
|
454
461
|
buf += chunk.toString();
|
|
455
462
|
});
|
|
456
463
|
return {
|
|
457
|
-
promise: new Promise((
|
|
464
|
+
promise: new Promise((resolve5, reject) => {
|
|
458
465
|
child.on("error", reject);
|
|
459
466
|
child.on("close", () => {
|
|
460
|
-
|
|
467
|
+
resolve5(buf.split("\n").filter(Boolean));
|
|
461
468
|
});
|
|
462
469
|
})
|
|
463
470
|
};
|
|
@@ -621,13 +628,13 @@ var grepTool = {
|
|
|
621
628
|
}
|
|
622
629
|
};
|
|
623
630
|
async function detectRg(signal) {
|
|
624
|
-
return new Promise((
|
|
631
|
+
return new Promise((resolve5) => {
|
|
625
632
|
try {
|
|
626
633
|
const p = spawn("rg", ["--version"], { stdio: "ignore", signal });
|
|
627
|
-
p.on("error", () =>
|
|
628
|
-
p.on("close", (code) =>
|
|
634
|
+
p.on("error", () => resolve5(false));
|
|
635
|
+
p.on("close", (code) => resolve5(code === 0));
|
|
629
636
|
} catch {
|
|
630
|
-
|
|
637
|
+
resolve5(false);
|
|
631
638
|
}
|
|
632
639
|
});
|
|
633
640
|
}
|
|
@@ -882,12 +889,41 @@ var bashTool = {
|
|
|
882
889
|
signal: opts.signal
|
|
883
890
|
});
|
|
884
891
|
if (input.background) {
|
|
885
|
-
|
|
886
|
-
|
|
892
|
+
let buf2 = "";
|
|
893
|
+
let truncated = false;
|
|
894
|
+
const child2 = spawn(shell, args, {
|
|
895
|
+
cwd: ctx.projectRoot,
|
|
896
|
+
env,
|
|
897
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
898
|
+
detached: true,
|
|
899
|
+
signal: opts.signal
|
|
900
|
+
});
|
|
901
|
+
const pid = child2.pid;
|
|
902
|
+
child2.stdout?.on("data", (chunk) => {
|
|
903
|
+
if (!truncated) {
|
|
904
|
+
const remain = MAX_OUTPUT - buf2.length;
|
|
905
|
+
if (remain > 0) {
|
|
906
|
+
buf2 += chunk.toString().slice(0, remain);
|
|
907
|
+
}
|
|
908
|
+
if (buf2.length >= MAX_OUTPUT) truncated = true;
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
child2.stderr?.on("data", (chunk) => {
|
|
912
|
+
if (!truncated) {
|
|
913
|
+
const remain = MAX_OUTPUT - buf2.length;
|
|
914
|
+
if (remain > 0) {
|
|
915
|
+
buf2 += chunk.toString().slice(0, remain);
|
|
916
|
+
}
|
|
917
|
+
if (buf2.length >= MAX_OUTPUT) truncated = true;
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
child2.on("close", () => {
|
|
921
|
+
});
|
|
922
|
+
if (typeof pid === "number") child2.unref();
|
|
887
923
|
yield {
|
|
888
924
|
type: "final",
|
|
889
925
|
output: {
|
|
890
|
-
output:
|
|
926
|
+
output: truncated ? buf2.slice(0, MAX_OUTPUT) + "\u2026[truncated]" : buf2,
|
|
891
927
|
exit_code: null,
|
|
892
928
|
timed_out: false,
|
|
893
929
|
pid
|
|
@@ -932,6 +968,7 @@ var bashTool = {
|
|
|
932
968
|
}
|
|
933
969
|
}, 2e3);
|
|
934
970
|
timers.push(killTimer);
|
|
971
|
+
killTimer.unref?.();
|
|
935
972
|
} catch {
|
|
936
973
|
}
|
|
937
974
|
}
|
|
@@ -949,10 +986,10 @@ var bashTool = {
|
|
|
949
986
|
queue.push(c);
|
|
950
987
|
}
|
|
951
988
|
};
|
|
952
|
-
const next = () => new Promise((
|
|
989
|
+
const next = () => new Promise((resolve5) => {
|
|
953
990
|
const c = queue.shift();
|
|
954
|
-
if (c)
|
|
955
|
-
else resolveNext =
|
|
991
|
+
if (c) resolve5(c);
|
|
992
|
+
else resolveNext = resolve5;
|
|
956
993
|
});
|
|
957
994
|
let lastFlush = Date.now();
|
|
958
995
|
const flush = () => {
|
|
@@ -1051,7 +1088,7 @@ var ALLOWED_COMMANDS = {
|
|
|
1051
1088
|
cargo: ["--version", "build", "test", "check"],
|
|
1052
1089
|
rustc: ["--version"],
|
|
1053
1090
|
go: ["version", "run", "build", "test"],
|
|
1054
|
-
python: ["--version"
|
|
1091
|
+
python: ["--version"],
|
|
1055
1092
|
pip: ["--version", "install", "list"],
|
|
1056
1093
|
docker: ["--version", "ps", "images", "build"],
|
|
1057
1094
|
kubectl: ["version", "get", "describe", "logs"]
|
|
@@ -1059,6 +1096,30 @@ var ALLOWED_COMMANDS = {
|
|
|
1059
1096
|
var MAX_ARGS = 20;
|
|
1060
1097
|
var MAX_OUTPUT2 = 2e5;
|
|
1061
1098
|
var TIMEOUT_MS = 3e4;
|
|
1099
|
+
var BLOCKED_ARG_PATTERNS = {
|
|
1100
|
+
// python -c/--command executes arbitrary code; python -m runs modules
|
|
1101
|
+
python: [/-c$/, /^--command$/, /^-m$/, /^--module$/],
|
|
1102
|
+
// git --exec=<cmd> runs arbitrary commands via upload-pack/receive-pack
|
|
1103
|
+
git: [/^--exec=/, /^--upload-pack=/, /^--receive-pack=/],
|
|
1104
|
+
// node -r/--require preloads arbitrary modules; --eval executes code
|
|
1105
|
+
node: [/^-r$/, /^--require$/, /^-e$/, /^--eval$/, /^--prof-process$/],
|
|
1106
|
+
// go run could execute arbitrary .go files; -ldflags could inject build-time code
|
|
1107
|
+
go: [/^-ldflags$/],
|
|
1108
|
+
// bun --preload is similar to node --require
|
|
1109
|
+
bun: [/^--preload$/]
|
|
1110
|
+
};
|
|
1111
|
+
function validateArgs(cmd, args) {
|
|
1112
|
+
const blocked = BLOCKED_ARG_PATTERNS[cmd];
|
|
1113
|
+
if (!blocked) return null;
|
|
1114
|
+
for (const arg of args) {
|
|
1115
|
+
for (const pattern of blocked) {
|
|
1116
|
+
if (pattern.test(arg)) {
|
|
1117
|
+
return `Blocked argument "${arg}" for command "${cmd}" (matches security pattern ${pattern})`;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1062
1123
|
var execTool = {
|
|
1063
1124
|
name: "exec",
|
|
1064
1125
|
category: "Shell",
|
|
@@ -1102,6 +1163,18 @@ var execTool = {
|
|
|
1102
1163
|
}
|
|
1103
1164
|
const args = (input.args ?? []).slice(0, MAX_ARGS);
|
|
1104
1165
|
const timeout = Math.max(1, Math.min(input.timeout ?? TIMEOUT_MS, TIMEOUT_MS));
|
|
1166
|
+
const argError = validateArgs(cmd, args);
|
|
1167
|
+
if (argError) {
|
|
1168
|
+
return {
|
|
1169
|
+
command: cmd,
|
|
1170
|
+
args,
|
|
1171
|
+
stdout: "",
|
|
1172
|
+
stderr: argError,
|
|
1173
|
+
exitCode: 1,
|
|
1174
|
+
truncated: false,
|
|
1175
|
+
allowed: false
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1105
1178
|
const requestedCwd = input.cwd ? path.resolve(ctx.projectRoot, input.cwd) : ctx.cwd;
|
|
1106
1179
|
const rel = path.relative(ctx.projectRoot, requestedCwd);
|
|
1107
1180
|
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
@@ -1121,7 +1194,7 @@ var execTool = {
|
|
|
1121
1194
|
}
|
|
1122
1195
|
};
|
|
1123
1196
|
function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
1124
|
-
return new Promise((
|
|
1197
|
+
return new Promise((resolve5) => {
|
|
1125
1198
|
let stdout = "";
|
|
1126
1199
|
let stderr = "";
|
|
1127
1200
|
let killed = false;
|
|
@@ -1143,7 +1216,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
1143
1216
|
});
|
|
1144
1217
|
child.on("close", (code) => {
|
|
1145
1218
|
clearTimeout(timer);
|
|
1146
|
-
|
|
1219
|
+
resolve5({
|
|
1147
1220
|
command: cmd,
|
|
1148
1221
|
args,
|
|
1149
1222
|
stdout: stdout.slice(0, MAX_OUTPUT2),
|
|
@@ -1155,7 +1228,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
1155
1228
|
});
|
|
1156
1229
|
child.on("error", (err) => {
|
|
1157
1230
|
clearTimeout(timer);
|
|
1158
|
-
|
|
1231
|
+
resolve5({
|
|
1159
1232
|
command: cmd,
|
|
1160
1233
|
args,
|
|
1161
1234
|
stdout: stdout.slice(0, MAX_OUTPUT2),
|
|
@@ -1946,7 +2019,7 @@ function buildArgs(input) {
|
|
|
1946
2019
|
}
|
|
1947
2020
|
}
|
|
1948
2021
|
function runGit(args, cwd, signal) {
|
|
1949
|
-
return new Promise((
|
|
2022
|
+
return new Promise((resolve5) => {
|
|
1950
2023
|
let stdout = "";
|
|
1951
2024
|
let stderr = "";
|
|
1952
2025
|
const child = spawn("git", args, {
|
|
@@ -1965,7 +2038,7 @@ function runGit(args, cwd, signal) {
|
|
|
1965
2038
|
}
|
|
1966
2039
|
});
|
|
1967
2040
|
child.on("error", (err) => {
|
|
1968
|
-
|
|
2041
|
+
resolve5({
|
|
1969
2042
|
command: args[0],
|
|
1970
2043
|
stdout,
|
|
1971
2044
|
stderr: err.message,
|
|
@@ -1974,7 +2047,7 @@ function runGit(args, cwd, signal) {
|
|
|
1974
2047
|
});
|
|
1975
2048
|
});
|
|
1976
2049
|
child.on("close", (code) => {
|
|
1977
|
-
|
|
2050
|
+
resolve5({
|
|
1978
2051
|
command: args[0],
|
|
1979
2052
|
stdout: stdout.slice(0, MAX_OUTPUT3),
|
|
1980
2053
|
stderr: stderr.slice(0, MAX_OUTPUT3),
|
|
@@ -2023,7 +2096,7 @@ var patchTool = {
|
|
|
2023
2096
|
};
|
|
2024
2097
|
}
|
|
2025
2098
|
}
|
|
2026
|
-
const tmpDir = await fs4.mkdtemp(path.join(
|
|
2099
|
+
const tmpDir = await fs4.mkdtemp(path.join(os.tmpdir(), ".wstack_patch_"));
|
|
2027
2100
|
try {
|
|
2028
2101
|
await fs4.chmod(tmpDir, 448).catch(() => {
|
|
2029
2102
|
});
|
|
@@ -2070,7 +2143,7 @@ function stripPathComponents(p, strip) {
|
|
|
2070
2143
|
return parts.slice(strip).join("/");
|
|
2071
2144
|
}
|
|
2072
2145
|
function runPatch(args, cwd, signal) {
|
|
2073
|
-
return new Promise((
|
|
2146
|
+
return new Promise((resolve5) => {
|
|
2074
2147
|
let stdout = "";
|
|
2075
2148
|
let stderr = "";
|
|
2076
2149
|
const env = { ...buildChildEnv(), LANG: "C", LC_ALL: "C" };
|
|
@@ -2081,8 +2154,8 @@ function runPatch(args, cwd, signal) {
|
|
|
2081
2154
|
child.stderr?.on("data", (c) => {
|
|
2082
2155
|
stderr += c.toString();
|
|
2083
2156
|
});
|
|
2084
|
-
child.on("close", (code) =>
|
|
2085
|
-
child.on("error", (e) =>
|
|
2157
|
+
child.on("close", (code) => resolve5({ exitCode: code ?? 1, stdout, stderr }));
|
|
2158
|
+
child.on("error", (e) => resolve5({ exitCode: 1, stdout: "", stderr: e.message }));
|
|
2086
2159
|
});
|
|
2087
2160
|
}
|
|
2088
2161
|
function extractPatchedFiles(output) {
|
|
@@ -2284,7 +2357,7 @@ function findGitDir2(cwd) {
|
|
|
2284
2357
|
return null;
|
|
2285
2358
|
}
|
|
2286
2359
|
function runGit2(args, cwd, signal) {
|
|
2287
|
-
return new Promise((
|
|
2360
|
+
return new Promise((resolve5) => {
|
|
2288
2361
|
let stdout = "";
|
|
2289
2362
|
let stderr = "";
|
|
2290
2363
|
const child = spawn("git", args, { cwd, signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -2294,8 +2367,8 @@ function runGit2(args, cwd, signal) {
|
|
|
2294
2367
|
child.stderr?.on("data", (c) => {
|
|
2295
2368
|
stderr += c.toString();
|
|
2296
2369
|
});
|
|
2297
|
-
child.on("close", (code) =>
|
|
2298
|
-
child.on("error", (e) =>
|
|
2370
|
+
child.on("close", (code) => resolve5({ stdout, stderr, exitCode: code ?? 0 }));
|
|
2371
|
+
child.on("error", (e) => resolve5({ stdout: "", stderr: e.message, exitCode: 1 }));
|
|
2299
2372
|
});
|
|
2300
2373
|
}
|
|
2301
2374
|
async function fileDiff(input, ctx, signal) {
|
|
@@ -2542,8 +2615,8 @@ async function* spawnStream(opts) {
|
|
|
2542
2615
|
let spawnFailed = false;
|
|
2543
2616
|
for (; ; ) {
|
|
2544
2617
|
while (queue.length === 0) {
|
|
2545
|
-
await new Promise((
|
|
2546
|
-
waiter =
|
|
2618
|
+
await new Promise((resolve5) => {
|
|
2619
|
+
waiter = resolve5;
|
|
2547
2620
|
});
|
|
2548
2621
|
}
|
|
2549
2622
|
const chunk = queue.shift();
|
|
@@ -3047,6 +3120,22 @@ var installTool = {
|
|
|
3047
3120
|
const pkgList = input.packages ? (Array.isArray(input.packages) ? input.packages : input.packages.split(",")).map(
|
|
3048
3121
|
(p) => p.trim()
|
|
3049
3122
|
) : [];
|
|
3123
|
+
const PKG_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/i;
|
|
3124
|
+
for (const pkg of pkgList) {
|
|
3125
|
+
if (!PKG_NAME_RE.test(pkg) || pkg.startsWith("-")) {
|
|
3126
|
+
yield {
|
|
3127
|
+
type: "final",
|
|
3128
|
+
output: {
|
|
3129
|
+
packages: pkgList,
|
|
3130
|
+
exit_code: 1,
|
|
3131
|
+
output: `Invalid package name "${pkg}". Names must match ${PKG_NAME_RE} and not start with "-".`,
|
|
3132
|
+
dry_run: Boolean(input.dry_run),
|
|
3133
|
+
truncated: false
|
|
3134
|
+
}
|
|
3135
|
+
};
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3050
3139
|
if (pkgList.length > 0) args.push(...pkgList);
|
|
3051
3140
|
yield {
|
|
3052
3141
|
type: "log",
|
|
@@ -3247,7 +3336,7 @@ async function detectManager2(cwd) {
|
|
|
3247
3336
|
return "npm";
|
|
3248
3337
|
}
|
|
3249
3338
|
function runOutdated(manager, args, cwd, signal) {
|
|
3250
|
-
return new Promise((
|
|
3339
|
+
return new Promise((resolve5) => {
|
|
3251
3340
|
let stdout = "";
|
|
3252
3341
|
let stderr = "";
|
|
3253
3342
|
const MAX = 1e5;
|
|
@@ -3260,11 +3349,11 @@ function runOutdated(manager, args, cwd, signal) {
|
|
|
3260
3349
|
});
|
|
3261
3350
|
child.on("close", (code) => {
|
|
3262
3351
|
const result = parseOutdatedOutput(stdout, code ?? 0);
|
|
3263
|
-
|
|
3352
|
+
resolve5(result);
|
|
3264
3353
|
});
|
|
3265
3354
|
child.on(
|
|
3266
3355
|
"error",
|
|
3267
|
-
(e) =>
|
|
3356
|
+
(e) => resolve5({
|
|
3268
3357
|
exit_code: 1,
|
|
3269
3358
|
packages: [],
|
|
3270
3359
|
total: 0,
|
|
@@ -3378,8 +3467,17 @@ var logsTool = {
|
|
|
3378
3467
|
async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
3379
3468
|
const args = ["logs"];
|
|
3380
3469
|
if (lines > 0) args.push("--tail", String(lines));
|
|
3470
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._:-]+$/.test(service)) {
|
|
3471
|
+
return {
|
|
3472
|
+
source: `docker:${service}`,
|
|
3473
|
+
entries: [],
|
|
3474
|
+
total: 0,
|
|
3475
|
+
truncated: false,
|
|
3476
|
+
stream_mode: false
|
|
3477
|
+
};
|
|
3478
|
+
}
|
|
3381
3479
|
args.push("--timestamps", service);
|
|
3382
|
-
return new Promise((
|
|
3480
|
+
return new Promise((resolve5) => {
|
|
3383
3481
|
let stdout = "";
|
|
3384
3482
|
let stderr = "";
|
|
3385
3483
|
const MAX = 2e5;
|
|
@@ -3393,7 +3491,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
3393
3491
|
child.on("close", (code) => {
|
|
3394
3492
|
const output = stdout + stderr;
|
|
3395
3493
|
const entries = parseLogLines(output, filterRe);
|
|
3396
|
-
|
|
3494
|
+
resolve5({
|
|
3397
3495
|
source: `docker:${service}`,
|
|
3398
3496
|
entries,
|
|
3399
3497
|
total: entries.length,
|
|
@@ -3403,7 +3501,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
3403
3501
|
});
|
|
3404
3502
|
child.on(
|
|
3405
3503
|
"error",
|
|
3406
|
-
(e) =>
|
|
3504
|
+
(e) => resolve5({
|
|
3407
3505
|
source: `docker:${service}`,
|
|
3408
3506
|
entries: [],
|
|
3409
3507
|
total: 0,
|
|
@@ -3764,7 +3862,7 @@ var scaffoldTool = {
|
|
|
3764
3862
|
const vars = { name, ...input.vars };
|
|
3765
3863
|
const builtIn = BUILT_IN_TEMPLATES[input.template];
|
|
3766
3864
|
if (builtIn) {
|
|
3767
|
-
return await handleBuiltIn(name, builtIn.files, cwd, input.dry_run ?? false, vars);
|
|
3865
|
+
return await handleBuiltIn(name, builtIn.files, cwd, ctx, input.dry_run ?? false, vars);
|
|
3768
3866
|
}
|
|
3769
3867
|
return {
|
|
3770
3868
|
template: input.template,
|
|
@@ -3776,12 +3874,19 @@ var scaffoldTool = {
|
|
|
3776
3874
|
};
|
|
3777
3875
|
}
|
|
3778
3876
|
};
|
|
3779
|
-
async function handleBuiltIn(name, templateFiles, cwd, dryRun, vars) {
|
|
3877
|
+
async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
|
|
3780
3878
|
const files = [];
|
|
3781
3879
|
let filesCreated = 0;
|
|
3782
3880
|
for (const [filePath, content] of Object.entries(templateFiles)) {
|
|
3783
3881
|
const resolvedPath = substituteVars(filePath, name, vars);
|
|
3784
|
-
const
|
|
3882
|
+
const joinedPath = path.join(cwd, resolvedPath);
|
|
3883
|
+
const root = path.resolve(ctx.projectRoot);
|
|
3884
|
+
const target = path.resolve(joinedPath);
|
|
3885
|
+
const rel = path.relative(root, target);
|
|
3886
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
3887
|
+
throw new Error(`scaffold: generated path "${resolvedPath}" would escape project root`);
|
|
3888
|
+
}
|
|
3889
|
+
const fullPath = target;
|
|
3785
3890
|
if (!dryRun) {
|
|
3786
3891
|
await fs4.mkdir(path.dirname(fullPath), { recursive: true });
|
|
3787
3892
|
await fs4.writeFile(fullPath, substituteVars(content, name, vars), "utf8");
|