@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 CHANGED
@@ -61,8 +61,8 @@ async function* spawnStream(opts) {
61
61
  let spawnFailed = false;
62
62
  for (; ; ) {
63
63
  while (queue.length === 0) {
64
- await new Promise((resolve4) => {
65
- waiter = resolve4;
64
+ await new Promise((resolve5) => {
65
+ waiter = resolve5;
66
66
  });
67
67
  }
68
68
  const chunk = queue.shift();
@@ -351,10 +351,10 @@ var bashTool = {
351
351
  queue.push(c);
352
352
  }
353
353
  };
354
- const next = () => new Promise((resolve4) => {
354
+ const next = () => new Promise((resolve5) => {
355
355
  const c = queue.shift();
356
- if (c) resolve4(c);
357
- else resolveNext = resolve4;
356
+ if (c) resolve5(c);
357
+ else resolveNext = resolve5;
358
358
  });
359
359
  let lastFlush = Date.now();
360
360
  const flush = () => {
@@ -589,7 +589,7 @@ function findGitDir(cwd) {
589
589
  return null;
590
590
  }
591
591
  function runGit(args, cwd, signal) {
592
- return new Promise((resolve4) => {
592
+ return new Promise((resolve5) => {
593
593
  let stdout = "";
594
594
  let stderr = "";
595
595
  const child = spawn("git", args, { cwd, signal, stdio: ["ignore", "pipe", "pipe"] });
@@ -599,8 +599,8 @@ function runGit(args, cwd, signal) {
599
599
  child.stderr?.on("data", (c) => {
600
600
  stderr += c.toString();
601
601
  });
602
- child.on("close", (code) => resolve4({ stdout, stderr, exitCode: code ?? 0 }));
603
- child.on("error", (e) => resolve4({ stdout: "", stderr: e.message, exitCode: 1 }));
602
+ child.on("close", (code) => resolve5({ stdout, stderr, exitCode: code ?? 0 }));
603
+ child.on("error", (e) => resolve5({ stdout: "", stderr: e.message, exitCode: 1 }));
604
604
  });
605
605
  }
606
606
  async function fileDiff(input, ctx, signal) {
@@ -934,7 +934,7 @@ var ALLOWED_COMMANDS = {
934
934
  cargo: ["--version", "build", "test", "check"],
935
935
  rustc: ["--version"],
936
936
  go: ["version", "run", "build", "test"],
937
- python: ["--version", "-c"],
937
+ python: ["--version"],
938
938
  pip: ["--version", "install", "list"],
939
939
  docker: ["--version", "ps", "images", "build"],
940
940
  kubectl: ["version", "get", "describe", "logs"]
@@ -942,6 +942,30 @@ var ALLOWED_COMMANDS = {
942
942
  var MAX_ARGS = 20;
943
943
  var MAX_OUTPUT2 = 2e5;
944
944
  var TIMEOUT_MS = 3e4;
945
+ var BLOCKED_ARG_PATTERNS = {
946
+ // python -c/--command executes arbitrary code; python -m runs modules
947
+ python: [/-c$/, /^--command$/, /^-m$/, /^--module$/],
948
+ // git --exec=<cmd> runs arbitrary commands via upload-pack/receive-pack
949
+ git: [/^--exec=/, /^--upload-pack=/, /^--receive-pack=/],
950
+ // node -r/--require preloads arbitrary modules; --eval executes code
951
+ node: [/^-r$/, /^--require$/, /^-e$/, /^--eval$/, /^--prof-process$/],
952
+ // go run could execute arbitrary .go files; -ldflags could inject build-time code
953
+ go: [/^-ldflags$/],
954
+ // bun --preload is similar to node --require
955
+ bun: [/^--preload$/]
956
+ };
957
+ function validateArgs(cmd, args) {
958
+ const blocked = BLOCKED_ARG_PATTERNS[cmd];
959
+ if (!blocked) return null;
960
+ for (const arg of args) {
961
+ for (const pattern of blocked) {
962
+ if (pattern.test(arg)) {
963
+ return `Blocked argument "${arg}" for command "${cmd}" (matches security pattern ${pattern})`;
964
+ }
965
+ }
966
+ }
967
+ return null;
968
+ }
945
969
  var execTool = {
946
970
  name: "exec",
947
971
  category: "Shell",
@@ -985,6 +1009,18 @@ var execTool = {
985
1009
  }
986
1010
  const args = (input.args ?? []).slice(0, MAX_ARGS);
987
1011
  const timeout = Math.max(1, Math.min(input.timeout ?? TIMEOUT_MS, TIMEOUT_MS));
1012
+ const argError = validateArgs(cmd, args);
1013
+ if (argError) {
1014
+ return {
1015
+ command: cmd,
1016
+ args,
1017
+ stdout: "",
1018
+ stderr: argError,
1019
+ exitCode: 1,
1020
+ truncated: false,
1021
+ allowed: false
1022
+ };
1023
+ }
988
1024
  const requestedCwd = input.cwd ? path.resolve(ctx.projectRoot, input.cwd) : ctx.cwd;
989
1025
  const rel = path.relative(ctx.projectRoot, requestedCwd);
990
1026
  if (rel.startsWith("..") || path.isAbsolute(rel)) {
@@ -1004,7 +1040,7 @@ var execTool = {
1004
1040
  }
1005
1041
  };
1006
1042
  function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
1007
- return new Promise((resolve4) => {
1043
+ return new Promise((resolve5) => {
1008
1044
  let stdout = "";
1009
1045
  let stderr = "";
1010
1046
  let killed = false;
@@ -1026,7 +1062,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
1026
1062
  });
1027
1063
  child.on("close", (code) => {
1028
1064
  clearTimeout(timer);
1029
- resolve4({
1065
+ resolve5({
1030
1066
  command: cmd,
1031
1067
  args,
1032
1068
  stdout: stdout.slice(0, MAX_OUTPUT2),
@@ -1038,7 +1074,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId) {
1038
1074
  });
1039
1075
  child.on("error", (err) => {
1040
1076
  clearTimeout(timer);
1041
- resolve4({
1077
+ resolve5({
1042
1078
  command: cmd,
1043
1079
  args,
1044
1080
  stdout: stdout.slice(0, MAX_OUTPUT2),
@@ -1565,7 +1601,7 @@ function buildArgs(input) {
1565
1601
  }
1566
1602
  }
1567
1603
  function runGit2(args, cwd, signal) {
1568
- return new Promise((resolve4) => {
1604
+ return new Promise((resolve5) => {
1569
1605
  let stdout = "";
1570
1606
  let stderr = "";
1571
1607
  const child = spawn("git", args, {
@@ -1584,7 +1620,7 @@ function runGit2(args, cwd, signal) {
1584
1620
  }
1585
1621
  });
1586
1622
  child.on("error", (err) => {
1587
- resolve4({
1623
+ resolve5({
1588
1624
  command: args[0],
1589
1625
  stdout,
1590
1626
  stderr: err.message,
@@ -1593,7 +1629,7 @@ function runGit2(args, cwd, signal) {
1593
1629
  });
1594
1630
  });
1595
1631
  child.on("close", (code) => {
1596
- resolve4({
1632
+ resolve5({
1597
1633
  command: args[0],
1598
1634
  stdout: stdout.slice(0, MAX_OUTPUT3),
1599
1635
  stderr: stderr.slice(0, MAX_OUTPUT3),
@@ -1774,13 +1810,13 @@ var grepTool = {
1774
1810
  }
1775
1811
  };
1776
1812
  async function detectRg(signal) {
1777
- return new Promise((resolve4) => {
1813
+ return new Promise((resolve5) => {
1778
1814
  try {
1779
1815
  const p = spawn("rg", ["--version"], { stdio: "ignore", signal });
1780
- p.on("error", () => resolve4(false));
1781
- p.on("close", (code) => resolve4(code === 0));
1816
+ p.on("error", () => resolve5(false));
1817
+ p.on("close", (code) => resolve5(code === 0));
1782
1818
  } catch {
1783
- resolve4(false);
1819
+ resolve5(false);
1784
1820
  }
1785
1821
  });
1786
1822
  }
@@ -2039,6 +2075,22 @@ var installTool = {
2039
2075
  const pkgList = input.packages ? (Array.isArray(input.packages) ? input.packages : input.packages.split(",")).map(
2040
2076
  (p) => p.trim()
2041
2077
  ) : [];
2078
+ const PKG_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/i;
2079
+ for (const pkg of pkgList) {
2080
+ if (!PKG_NAME_RE.test(pkg) || pkg.startsWith("-")) {
2081
+ yield {
2082
+ type: "final",
2083
+ output: {
2084
+ packages: pkgList,
2085
+ exit_code: 1,
2086
+ output: `Invalid package name "${pkg}". Names must match ${PKG_NAME_RE} and not start with "-".`,
2087
+ dry_run: Boolean(input.dry_run),
2088
+ truncated: false
2089
+ }
2090
+ };
2091
+ return;
2092
+ }
2093
+ }
2042
2094
  if (pkgList.length > 0) args.push(...pkgList);
2043
2095
  yield {
2044
2096
  type: "log",
@@ -2361,8 +2413,17 @@ var logsTool = {
2361
2413
  async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
2362
2414
  const args = ["logs"];
2363
2415
  if (lines > 0) args.push("--tail", String(lines));
2416
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._:-]+$/.test(service)) {
2417
+ return {
2418
+ source: `docker:${service}`,
2419
+ entries: [],
2420
+ total: 0,
2421
+ truncated: false,
2422
+ stream_mode: false
2423
+ };
2424
+ }
2364
2425
  args.push("--timestamps", service);
2365
- return new Promise((resolve4) => {
2426
+ return new Promise((resolve5) => {
2366
2427
  let stdout = "";
2367
2428
  let stderr = "";
2368
2429
  const MAX = 2e5;
@@ -2376,7 +2437,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
2376
2437
  child.on("close", (code) => {
2377
2438
  const output = stdout + stderr;
2378
2439
  const entries = parseLogLines(output, filterRe);
2379
- resolve4({
2440
+ resolve5({
2380
2441
  source: `docker:${service}`,
2381
2442
  entries,
2382
2443
  total: entries.length,
@@ -2386,7 +2447,7 @@ async function dockerLogs(service, lines, filterRe, cwd, signal, since) {
2386
2447
  });
2387
2448
  child.on(
2388
2449
  "error",
2389
- (e) => resolve4({
2450
+ (e) => resolve5({
2390
2451
  source: `docker:${service}`,
2391
2452
  entries: [],
2392
2453
  total: 0,
@@ -2520,7 +2581,7 @@ async function detectManager2(cwd) {
2520
2581
  return "npm";
2521
2582
  }
2522
2583
  function runOutdated(manager, args, cwd, signal) {
2523
- return new Promise((resolve4) => {
2584
+ return new Promise((resolve5) => {
2524
2585
  let stdout = "";
2525
2586
  let stderr = "";
2526
2587
  const MAX = 1e5;
@@ -2533,11 +2594,11 @@ function runOutdated(manager, args, cwd, signal) {
2533
2594
  });
2534
2595
  child.on("close", (code) => {
2535
2596
  const result = parseOutdatedOutput(stdout, code ?? 0);
2536
- resolve4(result);
2597
+ resolve5(result);
2537
2598
  });
2538
2599
  child.on(
2539
2600
  "error",
2540
- (e) => resolve4({
2601
+ (e) => resolve5({
2541
2602
  exit_code: 1,
2542
2603
  packages: [],
2543
2604
  total: 0,
@@ -2667,7 +2728,7 @@ function stripPathComponents(p, strip) {
2667
2728
  return parts.slice(strip).join("/");
2668
2729
  }
2669
2730
  function runPatch(args, cwd, signal) {
2670
- return new Promise((resolve4) => {
2731
+ return new Promise((resolve5) => {
2671
2732
  let stdout = "";
2672
2733
  let stderr = "";
2673
2734
  const env = { ...buildChildEnv(), LANG: "C", LC_ALL: "C" };
@@ -2678,8 +2739,8 @@ function runPatch(args, cwd, signal) {
2678
2739
  child.stderr?.on("data", (c) => {
2679
2740
  stderr += c.toString();
2680
2741
  });
2681
- child.on("close", (code) => resolve4({ exitCode: code ?? 1, stdout, stderr }));
2682
- child.on("error", (e) => resolve4({ exitCode: 1, stdout: "", stderr: e.message }));
2742
+ child.on("close", (code) => resolve5({ exitCode: code ?? 1, stdout, stderr }));
2743
+ child.on("error", (e) => resolve5({ exitCode: 1, stdout: "", stderr: e.message }));
2683
2744
  });
2684
2745
  }
2685
2746
  function extractPatchedFiles(output) {
@@ -2975,13 +3036,13 @@ async function globFiles(pattern, base, extraGlob) {
2975
3036
  return await globNative(pattern, base, extraGlob);
2976
3037
  }
2977
3038
  function checkRg() {
2978
- return new Promise((resolve4) => {
3039
+ return new Promise((resolve5) => {
2979
3040
  try {
2980
3041
  const p = spawn("rg", ["--version"], { stdio: "ignore" });
2981
- p.on("error", () => resolve4(false));
2982
- p.on("close", (code) => resolve4(code === 0));
3042
+ p.on("error", () => resolve5(false));
3043
+ p.on("close", (code) => resolve5(code === 0));
2983
3044
  } catch {
2984
- resolve4(false);
3045
+ resolve5(false);
2985
3046
  }
2986
3047
  });
2987
3048
  }
@@ -2993,10 +3054,10 @@ function spawnRgFind(pattern, base) {
2993
3054
  buf += chunk.toString();
2994
3055
  });
2995
3056
  return {
2996
- promise: new Promise((resolve4, reject) => {
3057
+ promise: new Promise((resolve5, reject) => {
2997
3058
  child.on("error", reject);
2998
3059
  child.on("close", () => {
2999
- resolve4(buf.split("\n").filter(Boolean));
3060
+ resolve5(buf.split("\n").filter(Boolean));
3000
3061
  });
3001
3062
  })
3002
3063
  };
@@ -3160,7 +3221,7 @@ var scaffoldTool = {
3160
3221
  const vars = { name, ...input.vars };
3161
3222
  const builtIn = BUILT_IN_TEMPLATES[input.template];
3162
3223
  if (builtIn) {
3163
- return await handleBuiltIn(name, builtIn.files, cwd, input.dry_run ?? false, vars);
3224
+ return await handleBuiltIn(name, builtIn.files, cwd, ctx, input.dry_run ?? false, vars);
3164
3225
  }
3165
3226
  return {
3166
3227
  template: input.template,
@@ -3172,12 +3233,19 @@ var scaffoldTool = {
3172
3233
  };
3173
3234
  }
3174
3235
  };
3175
- async function handleBuiltIn(name, templateFiles, cwd, dryRun, vars) {
3236
+ async function handleBuiltIn(name, templateFiles, cwd, ctx, dryRun, vars) {
3176
3237
  const files = [];
3177
3238
  let filesCreated = 0;
3178
3239
  for (const [filePath, content] of Object.entries(templateFiles)) {
3179
3240
  const resolvedPath = substituteVars(filePath, name, vars);
3180
- const fullPath = path.join(cwd, resolvedPath);
3241
+ const joinedPath = path.join(cwd, resolvedPath);
3242
+ const root = path.resolve(ctx.projectRoot);
3243
+ const target = path.resolve(joinedPath);
3244
+ const rel = path.relative(root, target);
3245
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
3246
+ throw new Error(`scaffold: generated path "${resolvedPath}" would escape project root`);
3247
+ }
3248
+ const fullPath = target;
3181
3249
  if (!dryRun) {
3182
3250
  await fs9.mkdir(path.dirname(fullPath), { recursive: true });
3183
3251
  await fs9.writeFile(fullPath, substituteVars(content, name, vars), "utf8");