JuliusBrussee/caveman · error · Error

cannot safely launch Windows command shim: ${executable}

Error message

cannot safely launch Windows command shim: ${executable}

What it means

On win32, portable invocation resolves .cmd/.bat shims (the wrappers npm creates for bin scripts) and inspects them before executing, because spawning a .cmd directly lets attackers inject arbitrary commands via the shim body. If the resolved file is not a regular file or exceeds 256 KiB, the library refuses to launch it with 'cannot safely launch Windows command shim'. This is a deliberate mitigation for CVE-2024-27980-class attacks.

Source

Thrown at packages/agent/src/portable-process.ts:56

}

export function portableInvocation(
  command: string,
  args: readonly string[],
  options: {
    platform?: NodeJS.Platform;
    env?: NodeJS.ProcessEnv;
    execPath?: string;
  } = {},
): PortableInvocation {
  const platform = options.platform ?? process.platform;
  const env = options.env ?? process.env;
  if (platform !== "win32") return { command, args: [...args] };
  const executable = resolveWindowsCommand(command, env) ?? command;
  if (!/\.(?:cmd|bat)$/i.test(executable)) return { command: executable, args: [...args] };
  const stat = statSync(executable);
  if (!stat.isFile() || stat.size > 256 * 1024) {
    throw new Error(`cannot safely launch Windows command shim: ${executable}`);
  }
  const relativeScript = parseWindowsNodeShim(readFileSync(executable, "utf8"));
  if (!relativeScript) {
    throw new Error(`cannot safely launch non-Node Windows command shim: ${executable}`);
  }
  const script = resolve(dirname(executable), ...relativeScript.split(/[\\/]+/));
  if (!statSync(script).isFile()) throw new Error(`Windows command shim target is missing: ${script}`);
  return { command: options.execPath ?? process.execPath, args: [script, ...args] };
}

export function hostShellInvocation(
  source: string,
  platform: NodeJS.Platform = process.platform,
  env: NodeJS.ProcessEnv = process.env,
): PortableInvocation {
  if (platform === "win32") {
    return {
      command: envValue(env, "ComSpec") ?? "cmd.exe",

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Reinstall the package that owns the shim so npm regenerates a sane, small wrapper: npm reinstall <pkg> (or delete node_modules/.bin/<name> and npm install)
  2. Verify the target: it must be a regular file well under 256 KiB
  3. Invoke the underlying node script (the shim's target) directly instead of the .cmd
  4. If you control the shim, trim it — legitimate npm shims are a few hundred bytes

Example fix

// before
const inv = portableInvocation("my-tool", []); // my-tool.cmd is 300 KiB or not a regular file

// after
// reinstall to restore the small generated shim
// $ npm install my-tool --force
const inv = portableInvocation("my-tool", []);
Defensive patterns

Strategy: try-catch

Validate before calling

import { statSync } from "node:fs";
function isLaunchableShim(executable: string): boolean {
  if (!/\.(?:cmd|bat)$/i.test(executable)) return true;
  try {
    const s = statSync(executable);
    return s.isFile() && s.size <= 256 * 1024;
  } catch {
    return false;
  }
}

Try / catch

try {
  const inv = portableInvocation(cmd, args);
} catch (e) {
  if (e instanceof Error && e.message.startsWith("cannot safely launch Windows command shim")) {
    // reinstall the owning package or fall back to node <script.js> directly
  }
  throw e;
}

Prevention

When it happens

Trigger: Running on Windows (or with options.platform forced to "win32") where the command resolves to a .cmd/.bat that is actually a directory, a symlink/pipe rather than a regular file, or a batch file larger than 256 KiB.

Common situations: A corrupted or hand-edited npm shim grew beyond the size cap, an antivirus quarantine left a stub, a symlinked global bin directory, or tests that fabricate oversized fake .cmd files to exercise this path.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/d5a9d74488467b9d. Report an issue: GitHub.