@wrongstack/tools 0.3.2 → 0.3.4
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/builtin.js +105 -37
- package/dist/builtin.js.map +1 -1
- package/dist/exec.js +37 -1
- package/dist/exec.js.map +1 -1
- package/dist/index.js +105 -37
- 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 +105 -37
- package/dist/pack.js.map +1 -1
- package/dist/scaffold.js +10 -3
- package/dist/scaffold.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
|
@@ -436,13 +436,13 @@ async function globFiles(pattern, base, extraGlob) {
|
|
|
436
436
|
return await globNative(pattern, base, extraGlob);
|
|
437
437
|
}
|
|
438
438
|
function checkRg() {
|
|
439
|
-
return new Promise((
|
|
439
|
+
return new Promise((resolve5) => {
|
|
440
440
|
try {
|
|
441
441
|
const p = spawn("rg", ["--version"], { stdio: "ignore" });
|
|
442
|
-
p.on("error", () =>
|
|
443
|
-
p.on("close", (code) =>
|
|
442
|
+
p.on("error", () => resolve5(false));
|
|
443
|
+
p.on("close", (code) => resolve5(code === 0));
|
|
444
444
|
} catch {
|
|
445
|
-
|
|
445
|
+
resolve5(false);
|
|
446
446
|
}
|
|
447
447
|
});
|
|
448
448
|
}
|
|
@@ -454,10 +454,10 @@ function spawnRgFind(pattern, base) {
|
|
|
454
454
|
buf += chunk.toString();
|
|
455
455
|
});
|
|
456
456
|
return {
|
|
457
|
-
promise: new Promise((
|
|
457
|
+
promise: new Promise((resolve5, reject) => {
|
|
458
458
|
child.on("error", reject);
|
|
459
459
|
child.on("close", () => {
|
|
460
|
-
|
|
460
|
+
resolve5(buf.split("\n").filter(Boolean));
|
|
461
461
|
});
|
|
462
462
|
})
|
|
463
463
|
};
|
|
@@ -621,13 +621,13 @@ var grepTool = {
|
|
|
621
621
|
}
|
|
622
622
|
};
|
|
623
623
|
async function detectRg(signal) {
|
|
624
|
-
return new Promise((
|
|
624
|
+
return new Promise((resolve5) => {
|
|
625
625
|
try {
|
|
626
626
|
const p = spawn("rg", ["--version"], { stdio: "ignore", signal });
|
|
627
|
-
p.on("error", () =>
|
|
628
|
-
p.on("close", (code) =>
|
|
627
|
+
p.on("error", () => resolve5(false));
|
|
628
|
+
p.on("close", (code) => resolve5(code === 0));
|
|
629
629
|
} catch {
|
|
630
|
-
|
|
630
|
+
resolve5(false);
|
|
631
631
|
}
|
|
632
632
|
});
|
|
633
633
|
}
|
|
@@ -949,10 +949,10 @@ var bashTool = {
|
|
|
949
949
|
queue.push(c);
|
|
950
950
|
}
|
|
951
951
|
};
|
|
952
|
-
const next = () => new Promise((
|
|
952
|
+
const next = () => new Promise((resolve5) => {
|
|
953
953
|
const c = queue.shift();
|
|
954
|
-
if (c)
|
|
955
|
-
else resolveNext =
|
|
954
|
+
if (c) resolve5(c);
|
|
955
|
+
else resolveNext = resolve5;
|
|
956
956
|
});
|
|
957
957
|
let lastFlush = Date.now();
|
|
958
958
|
const flush = () => {
|
|
@@ -1051,7 +1051,7 @@ var ALLOWED_COMMANDS = {
|
|
|
1051
1051
|
cargo: ["--version", "build", "test", "check"],
|
|
1052
1052
|
rustc: ["--version"],
|
|
1053
1053
|
go: ["version", "run", "build", "test"],
|
|
1054
|
-
python: ["--version"
|
|
1054
|
+
python: ["--version"],
|
|
1055
1055
|
pip: ["--version", "install", "list"],
|
|
1056
1056
|
docker: ["--version", "ps", "images", "build"],
|
|
1057
1057
|
kubectl: ["version", "get", "describe", "logs"]
|
|
@@ -1059,6 +1059,30 @@ var ALLOWED_COMMANDS = {
|
|
|
1059
1059
|
var MAX_ARGS = 20;
|
|
1060
1060
|
var MAX_OUTPUT2 = 2e5;
|
|
1061
1061
|
var TIMEOUT_MS = 3e4;
|
|
1062
|
+
var BLOCKED_ARG_PATTERNS = {
|
|
1063
|
+
// python -c/--command executes arbitrary code; python -m runs modules
|
|
1064
|
+
python: [/-c$/, /^--command$/, /^-m$/, /^--module$/],
|
|
1065
|
+
// git --exec=<cmd> runs arbitrary commands via upload-pack/receive-pack
|
|
1066
|
+
git: [/^--exec=/, /^--upload-pack=/, /^--receive-pack=/],
|
|
1067
|
+
// node -r/--require preloads arbitrary modules; --eval executes code
|
|
1068
|
+
node: [/^-r$/, /^--require$/, /^-e$/, /^--eval$/, /^--prof-process$/],
|
|
1069
|
+
// go run could execute arbitrary .go files; -ldflags could inject build-time code
|
|
1070
|
+
go: [/^-ldflags$/],
|
|
1071
|
+
// bun --preload is similar to node --require
|
|
1072
|
+
bun: [/^--preload$/]
|
|
1073
|
+
};
|
|
1074
|
+
function validateArgs(cmd, args) {
|
|
1075
|
+
const blocked = BLOCKED_ARG_PATTERNS[cmd];
|
|
1076
|
+
if (!blocked) return null;
|
|
1077
|
+
for (const arg of args) {
|
|
1078
|
+
for (const pattern of blocked) {
|
|
1079
|
+
if (pattern.test(arg)) {
|
|
1080
|
+
return `Blocked argument "${arg}" for command "${cmd}" (matches security pattern ${pattern})`;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1062
1086
|
var execTool = {
|
|
1063
1087
|
name: "exec",
|
|
1064
1088
|
category: "Shell",
|
|
@@ -1102,6 +1126,18 @@ var execTool = {
|
|
|
1102
1126
|
}
|
|
1103
1127
|
const args = (input.args ?? []).slice(0, MAX_ARGS);
|
|
1104
1128
|
const timeout = Math.max(1, Math.min(input.timeout ?? TIMEOUT_MS, TIMEOUT_MS));
|
|
1129
|
+
const argError = validateArgs(cmd, args);
|
|
1130
|
+
if (argError) {
|
|
1131
|
+
return {
|
|
1132
|
+
command: cmd,
|
|
1133
|
+
args,
|
|
1134
|
+
stdout: "",
|
|
1135
|
+
stderr: argError,
|
|
1136
|
+
exitCode: 1,
|
|
1137
|
+
truncated: false,
|
|
1138
|
+
allowed: false
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1105
1141
|
const requestedCwd = input.cwd ? path.resolve(ctx.projectRoot, input.cwd) : ctx.cwd;
|
|
1106
1142
|
const rel = path.relative(ctx.projectRoot, requestedCwd);
|
|
1107
1143
|
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
@@ -1121,7 +1157,7 @@ var execTool = {
|
|
|
1121
1157
|
}
|
|
1122
1158
|
};
|
|
1123
1159
|
function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
1124
|
-
return new Promise((
|
|
1160
|
+
return new Promise((resolve5) => {
|
|
1125
1161
|
let stdout = "";
|
|
1126
1162
|
let stderr = "";
|
|
1127
1163
|
let killed = false;
|
|
@@ -1143,7 +1179,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
1143
1179
|
});
|
|
1144
1180
|
child.on("close", (code) => {
|
|
1145
1181
|
clearTimeout(timer);
|
|
1146
|
-
|
|
1182
|
+
resolve5({
|
|
1147
1183
|
command: cmd,
|
|
1148
1184
|
args,
|
|
1149
1185
|
stdout: stdout.slice(0, MAX_OUTPUT2),
|
|
@@ -1155,7 +1191,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
|
|
|
1155
1191
|
});
|
|
1156
1192
|
child.on("error", (err) => {
|
|
1157
1193
|
clearTimeout(timer);
|
|
1158
|
-
|
|
1194
|
+
resolve5({
|
|
1159
1195
|
command: cmd,
|
|
1160
1196
|
args,
|
|
1161
1197
|
stdout: stdout.slice(0, MAX_OUTPUT2),
|
|
@@ -1946,7 +1982,7 @@ function buildArgs(input) {
|
|
|
1946
1982
|
}
|
|
1947
1983
|
}
|
|
1948
1984
|
function runGit(args, cwd, signal) {
|
|
1949
|
-
return new Promise((
|
|
1985
|
+
return new Promise((resolve5) => {
|
|
1950
1986
|
let stdout = "";
|
|
1951
1987
|
let stderr = "";
|
|
1952
1988
|
const child = spawn("git", args, {
|
|
@@ -1965,7 +2001,7 @@ function runGit(args, cwd, signal) {
|
|
|
1965
2001
|
}
|
|
1966
2002
|
});
|
|
1967
2003
|
child.on("error", (err) => {
|
|
1968
|
-
|
|
2004
|
+
resolve5({
|
|
1969
2005
|
command: args[0],
|
|
1970
2006
|
stdout,
|
|
1971
2007
|
stderr: err.message,
|
|
@@ -1974,7 +2010,7 @@ function runGit(args, cwd, signal) {
|
|
|
1974
2010
|
});
|
|
1975
2011
|
});
|
|
1976
2012
|
child.on("close", (code) => {
|
|
1977
|
-
|
|
2013
|
+
resolve5({
|
|
1978
2014
|
command: args[0],
|
|
1979
2015
|
stdout: stdout.slice(0, MAX_OUTPUT3),
|
|
1980
2016
|
stderr: stderr.slice(0, MAX_OUTPUT3),
|
|
@@ -2070,7 +2106,7 @@ function stripPathComponents(p, strip) {
|
|
|
2070
2106
|
return parts.slice(strip).join("/");
|
|
2071
2107
|
}
|
|
2072
2108
|
function runPatch(args, cwd, signal) {
|
|
2073
|
-
return new Promise((
|
|
2109
|
+
return new Promise((resolve5) => {
|
|
2074
2110
|
let stdout = "";
|
|
2075
2111
|
let stderr = "";
|
|
2076
2112
|
const env = { ...buildChildEnv(), LANG: "C", LC_ALL: "C" };
|
|
@@ -2081,8 +2117,8 @@ function runPatch(args, cwd, signal) {
|
|
|
2081
2117
|
child.stderr?.on("data", (c) => {
|
|
2082
2118
|
stderr += c.toString();
|
|
2083
2119
|
});
|
|
2084
|
-
child.on("close", (code) =>
|
|
2085
|
-
child.on("error", (e) =>
|
|
2120
|
+
child.on("close", (code) => resolve5({ exitCode: code ?? 1, stdout, stderr }));
|
|
2121
|
+
child.on("error", (e) => resolve5({ exitCode: 1, stdout: "", stderr: e.message }));
|
|
2086
2122
|
});
|
|
2087
2123
|
}
|
|
2088
2124
|
function extractPatchedFiles(output) {
|
|
@@ -2284,7 +2320,7 @@ function findGitDir2(cwd) {
|
|
|
2284
2320
|
return null;
|
|
2285
2321
|
}
|
|
2286
2322
|
function runGit2(args, cwd, signal) {
|
|
2287
|
-
return new Promise((
|
|
2323
|
+
return new Promise((resolve5) => {
|
|
2288
2324
|
let stdout = "";
|
|
2289
2325
|
let stderr = "";
|
|
2290
2326
|
const child = spawn("git", args, { cwd, signal, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -2294,8 +2330,8 @@ function runGit2(args, cwd, signal) {
|
|
|
2294
2330
|
child.stderr?.on("data", (c) => {
|
|
2295
2331
|
stderr += c.toString();
|
|
2296
2332
|
});
|
|
2297
|
-
child.on("close", (code) =>
|
|
2298
|
-
child.on("error", (e) =>
|
|
2333
|
+
child.on("close", (code) => resolve5({ stdout, stderr, exitCode: code ?? 0 }));
|
|
2334
|
+
child.on("error", (e) => resolve5({ stdout: "", stderr: e.message, exitCode: 1 }));
|
|
2299
2335
|
});
|
|
2300
2336
|
}
|
|
2301
2337
|
async function fileDiff(input, ctx, signal) {
|
|
@@ -2542,8 +2578,8 @@ async function* spawnStream(opts) {
|
|
|
2542
2578
|
let spawnFailed = false;
|
|
2543
2579
|
for (; ; ) {
|
|
2544
2580
|
while (queue.length === 0) {
|
|
2545
|
-
await new Promise((
|
|
2546
|
-
waiter =
|
|
2581
|
+
await new Promise((resolve5) => {
|
|
2582
|
+
waiter = resolve5;
|
|
2547
2583
|
});
|
|
2548
2584
|
}
|
|
2549
2585
|
const chunk = queue.shift();
|
|
@@ -3047,6 +3083,22 @@ var installTool = {
|
|
|
3047
3083
|
const pkgList = input.packages ? (Array.isArray(input.packages) ? input.packages : input.packages.split(",")).map(
|
|
3048
3084
|
(p) => p.trim()
|
|
3049
3085
|
) : [];
|
|
3086
|
+
const PKG_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/i;
|
|
3087
|
+
for (const pkg of pkgList) {
|
|
3088
|
+
if (!PKG_NAME_RE.test(pkg) || pkg.startsWith("-")) {
|
|
3089
|
+
yield {
|
|
3090
|
+
type: "final",
|
|
3091
|
+
output: {
|
|
3092
|
+
packages: pkgList,
|
|
3093
|
+
exit_code: 1,
|
|
3094
|
+
output: `Invalid package name "${pkg}". Names must match ${PKG_NAME_RE} and not start with "-".`,
|
|
3095
|
+
dry_run: Boolean(input.dry_run),
|
|
3096
|
+
truncated: false
|
|
3097
|
+
}
|
|
3098
|
+
};
|
|
3099
|
+
return;
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3050
3102
|
if (pkgList.length > 0) args.push(...pkgList);
|
|
3051
3103
|
yield {
|
|
3052
3104
|
type: "log",
|
|
@@ -3247,7 +3299,7 @@ async function detectManager2(cwd) {
|
|
|
3247
3299
|
return "npm";
|
|
3248
3300
|
}
|
|
3249
3301
|
function runOutdated(manager, args, cwd, signal) {
|
|
3250
|
-
return new Promise((
|
|
3302
|
+
return new Promise((resolve5) => {
|
|
3251
3303
|
let stdout = "";
|
|
3252
3304
|
let stderr = "";
|
|
3253
3305
|
const MAX = 1e5;
|
|
@@ -3260,11 +3312,11 @@ function runOutdated(manager, args, cwd, signal) {
|
|
|
3260
3312
|
});
|
|
3261
3313
|
child.on("close", (code) => {
|
|
3262
3314
|
const result = parseOutdatedOutput(stdout, code ?? 0);
|
|
3263
|
-
|
|
3315
|
+
resolve5(result);
|
|
3264
3316
|
});
|
|
3265
3317
|
child.on(
|
|
3266
3318
|
"error",
|
|
3267
|
-
(e) =>
|
|
3319
|
+
(e) => resolve5({
|
|
3268
3320
|
exit_code: 1,
|
|
3269
3321
|
packages: [],
|
|
3270
3322
|
total: 0,
|
|
@@ -3378,8 +3430,17 @@ var logsTool = {
|
|
|
3378
3430
|
async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
3379
3431
|
const args = ["logs"];
|
|
3380
3432
|
if (lines > 0) args.push("--tail", String(lines));
|
|
3433
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9._:-]+$/.test(service)) {
|
|
3434
|
+
return {
|
|
3435
|
+
source: `docker:${service}`,
|
|
3436
|
+
entries: [],
|
|
3437
|
+
total: 0,
|
|
3438
|
+
truncated: false,
|
|
3439
|
+
stream_mode: false
|
|
3440
|
+
};
|
|
3441
|
+
}
|
|
3381
3442
|
args.push("--timestamps", service);
|
|
3382
|
-
return new Promise((
|
|
3443
|
+
return new Promise((resolve5) => {
|
|
3383
3444
|
let stdout = "";
|
|
3384
3445
|
let stderr = "";
|
|
3385
3446
|
const MAX = 2e5;
|
|
@@ -3393,7 +3454,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
3393
3454
|
child.on("close", (code) => {
|
|
3394
3455
|
const output = stdout + stderr;
|
|
3395
3456
|
const entries = parseLogLines(output, filterRe);
|
|
3396
|
-
|
|
3457
|
+
resolve5({
|
|
3397
3458
|
source: `docker:${service}`,
|
|
3398
3459
|
entries,
|
|
3399
3460
|
total: entries.length,
|
|
@@ -3403,7 +3464,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
|
|
|
3403
3464
|
});
|
|
3404
3465
|
child.on(
|
|
3405
3466
|
"error",
|
|
3406
|
-
(e) =>
|
|
3467
|
+
(e) => resolve5({
|
|
3407
3468
|
source: `docker:${service}`,
|
|
3408
3469
|
entries: [],
|
|
3409
3470
|
total: 0,
|
|
@@ -3764,7 +3825,7 @@ var scaffoldTool = {
|
|
|
3764
3825
|
const vars = { name, ...input.vars };
|
|
3765
3826
|
const builtIn = BUILT_IN_TEMPLATES[input.template];
|
|
3766
3827
|
if (builtIn) {
|
|
3767
|
-
return await handleBuiltIn(name, builtIn.files, cwd, input.dry_run ?? false, vars);
|
|
3828
|
+
return await handleBuiltIn(name, builtIn.files, cwd, ctx, input.dry_run ?? false, vars);
|
|
3768
3829
|
}
|
|
3769
3830
|
return {
|
|
3770
3831
|
template: input.template,
|
|
@@ -3776,12 +3837,19 @@ var scaffoldTool = {
|
|
|
3776
3837
|
};
|
|
3777
3838
|
}
|
|
3778
3839
|
};
|
|
3779
|
-
async function handleBuiltIn(name, templateFiles, cwd, dryRun, vars) {
|
|
3840
|
+
async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
|
|
3780
3841
|
const files = [];
|
|
3781
3842
|
let filesCreated = 0;
|
|
3782
3843
|
for (const [filePath, content] of Object.entries(templateFiles)) {
|
|
3783
3844
|
const resolvedPath = substituteVars(filePath, name, vars);
|
|
3784
|
-
const
|
|
3845
|
+
const joinedPath = path.join(cwd, resolvedPath);
|
|
3846
|
+
const root = path.resolve(ctx.projectRoot);
|
|
3847
|
+
const target = path.resolve(joinedPath);
|
|
3848
|
+
const rel = path.relative(root, target);
|
|
3849
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
3850
|
+
throw new Error(`scaffold: generated path "${resolvedPath}" would escape project root`);
|
|
3851
|
+
}
|
|
3852
|
+
const fullPath = target;
|
|
3785
3853
|
if (!dryRun) {
|
|
3786
3854
|
await fs4.mkdir(path.dirname(fullPath), { recursive: true });
|
|
3787
3855
|
await fs4.writeFile(fullPath, substituteVars(content, name, vars), "utf8");
|