@wrongstack/acp 0.276.4 → 0.277.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/index.js CHANGED
@@ -6,6 +6,32 @@ import * as fsp from 'fs/promises';
6
6
  import * as path from 'path';
7
7
  import { spawn } from 'child_process';
8
8
 
9
+ // src/agent/stdio-transport.ts
10
+
11
+ // src/win32-cmd.ts
12
+ var WIN32_CMD_META = /[&|<>"\r\n\0]/;
13
+ function buildWin32CmdShimInvocation(command, args = []) {
14
+ assertSafeWin32CmdArgs([command, ...args]);
15
+ const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
16
+ return {
17
+ command: process.env["COMSPEC"] ?? "cmd.exe",
18
+ args: ["/d", "/c", line],
19
+ windowsVerbatimArguments: true
20
+ };
21
+ }
22
+ function assertSafeWin32CmdArgs(args) {
23
+ for (const arg of args) {
24
+ if (typeof arg === "string" && WIN32_CMD_META.test(arg)) {
25
+ throw new Error(
26
+ 'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
27
+ );
28
+ }
29
+ }
30
+ }
31
+ function quoteWin32CmdArg(arg) {
32
+ return `"${arg}"`;
33
+ }
34
+
9
35
  // src/agent/stdio-transport.ts
10
36
  var StdioTransport = class {
11
37
  stdin = process.stdin;
@@ -125,19 +151,14 @@ var ClientTransport = class {
125
151
  const isPkgLauncher = this.opts.command === "npx" || this.opts.command === "uvx";
126
152
  const spawnCwd = isPkgLauncher ? os.homedir() : this.opts.cwd;
127
153
  try {
128
- this.child = spawn3(this.opts.command, this.opts.args ?? [], {
154
+ const childArgs = this.opts.args ?? [];
155
+ const shim = process.platform === "win32" ? buildWin32CmdShimInvocation(this.opts.command, childArgs) : null;
156
+ this.child = spawn3(shim?.command ?? this.opts.command, shim?.args ?? childArgs, {
129
157
  env: { ...buildChildEnv(), ...this.opts.env },
130
158
  cwd: spawnCwd,
131
159
  stdio: ["pipe", "pipe", "pipe"],
132
160
  windowsHide: true,
133
- // On Windows, most ACP-supporting tools (claude, gemini, codex,
134
- // qwen, copilot) are installed as `.cmd` shims under
135
- // AppData\Roaming\npm\. Node's spawn won't find them via
136
- // `shell: false` because the .cmd extension is not in the
137
- // default PATHEXT lookup. The argv here is always from our
138
- // own static catalog or from a hardcoded spec, never from
139
- // user input, so shell-expansion is bounded.
140
- shell: process.platform === "win32"
161
+ ...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {}
141
162
  });
142
163
  } catch (err) {
143
164
  clearTimeout(timeout);
@@ -3209,16 +3230,12 @@ async function defaultProbe(desc, timeoutMs) {
3209
3230
  };
3210
3231
  let child;
3211
3232
  try {
3212
- child = spawn(desc.probe.command, [...desc.probe.args ?? []], {
3233
+ const probeArgs = [...desc.probe.args ?? []];
3234
+ const shim = process.platform === "win32" ? buildWin32CmdShimInvocation(desc.probe.command, probeArgs) : null;
3235
+ child = spawn(shim?.command ?? desc.probe.command, shim?.args ?? probeArgs, {
3213
3236
  stdio: ["ignore", "pipe", "pipe"],
3214
3237
  windowsHide: true,
3215
- // On Windows, `claude`, `gemini`, `npx`, etc. are typically
3216
- // installed as `.cmd` shims under AppData\Roaming\npm\. Node's
3217
- // spawn() will not find them without shell-mode unless the
3218
- // extension is present. `shell: true` resolves this for the
3219
- // common case. The probe argv is always from our static
3220
- // catalog, never user input, so shell-expansion is bounded.
3221
- shell: process.platform === "win32"
3238
+ ...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {}
3222
3239
  });
3223
3240
  } catch (err) {
3224
3241
  const msg = err instanceof Error ? err.message : String(err);