abhigyanpatwari/GitNexus · error · Error

argument contains NUL/CR/LF, unsafe for the Windows shell: $

Error message

argument contains NUL/CR/LF, unsafe for the Windows shell: ${JSON.stringify(arg)}

What it means

Thrown by quoteWin32Arg() when a Windows command-line argument contains a NUL (\0), carriage return (\r), or line feed (\n). These control characters cannot be represented safely through the three cmd.exe parsing layers (cmd /c, the npm.cmd %* re-parse, and the node CRT argv), so the helper rejects them outright as an injection defense rather than attempting to quote them. Part of the hardening that spawns the ONNX embedding runtime installer on Windows.

Source

Thrown at gitnexus/src/core/embeddings/runtime-install.ts:308

 * Quote a single argument for the Windows `cmd.exe` shell (#2372). npm is a
 * `.cmd` shim, so the spawn must go through a shell (EINVAL otherwise since
 * CVE-2024-27980), and Node does NOT escape args under `shell: true` — a spaced
 * `--prefix` path splits, and cmd eats the `^` in `@pkg@^1.0.0` semver ranges.
 *
 * Rules (validated against Node source, MS cmd/CRT docs, BatBadBut, Rust std):
 * reject NUL/CR/LF and embedded `"` (both unrepresentable/unsafe at the cmd
 * layer, and `"` is illegal in Windows paths and npm specs); wrap in double
 * quotes when empty or containing whitespace/metacharacters; double the trailing
 * backslash run so the added closing quote is not itself escaped (`C:\` →
 * `"C:\\"`). `^` is literal inside cmd double quotes across all three parse
 * layers (cmd `/c` → npm.cmd's `%*` re-parse → node CRT argv). Two documented
 * ceilings quoting can't close: a defined `%VAR%` expands once at the first cmd
 * parse, and `!` expands only under registry-enabled delayed expansion — both
 * are the env-var owner's trust, out of the malicious-repo threat model.
 */
export const quoteWin32Arg = (arg: string): string => {
  if (/[\0\r\n]/.test(arg)) {
    throw new Error(
      `argument contains NUL/CR/LF, unsafe for the Windows shell: ${JSON.stringify(arg)}`,
    );
  }
  if (arg.includes('"')) {
    throw new Error(
      `argument contains a double quote, unsafe for the Windows shell: ${JSON.stringify(arg)}`,
    );
  }
  if (arg !== '' && !WIN32_NEEDS_QUOTING.test(arg)) return arg;
  const trailingBackslashes = /\\*$/.exec(arg)?.[0].length ?? 0;
  return `"${arg}${'\\'.repeat(trailingBackslashes)}"`;
};

/**
 * Compose a full `cmd.exe` command line: the command stays unquoted (so
 * PATH/PATHEXT resolves a bare name or `.cmd` shim), args are individually
 * quoted. Passing this as spawn's first (only) string argument — no args array
 * — yields a byte-identical `cmd.exe /d /s /c "…"` line while avoiding DEP0190

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Sanitize the offending value before building the command line: strip or reject control characters (arg.replace(/[\0\r\n]+/g, '') or better, fail on them explicitly).
  2. Fix the source of the value: rename the directory/file whose name contains the line break, or trim CRLF from config values when parsing them.
  3. If you control the spawn, prefer spawn(file, [args]) with an args array (no shell) instead of composing a cmd.exe command line.
  4. Do not attempt to work around it by encoding the newline — the guard exists because no cmd.exe quoting can make these bytes safe.

Example fix

// before
const cmdline = composeWin32Cmd('npm', [rawPath]); // rawPath contains "repo\nname"
// -> Error: argument contains NUL/CR/LF, unsafe for the Windows shell: "repo\nname"

// after
if (/[\0\r\n]/.test(rawPath)) throw new Error(`invalid path: ${JSON.stringify(rawPath)}`);
const cleaned = rawPath.replace(/[\0\r\n]+/g, '');
const cmdline = composeWin32Cmd('npm', [cleaned]);
Defensive patterns

Strategy: validation

Validate before calling

// Reject control characters before any Windows command-line composition.
export function assertSafeWin32Arg(arg: string): void {
  if (/[\0\r\n]/.test(arg)) {
    throw new Error(
      `unsafe argument (NUL/CR/LF): ${JSON.stringify(arg)} — sanitize the source value`,
    );
  }
}

assertSafeWin32Arg(cacheDir);
assertSafeWin32Arg(pkgSpec);

Type guard

export const isShellSafeArg = (arg: string): boolean =>
  arg.length > 0 && ![...arg].some((c) => c.charCodeAt(0) < 32);

Try / catch

try {
  cmdline = composeWin32Cmd(npmBin, args);
} catch (err) {
  if (err instanceof Error && err.message.includes('NUL/CR/LF')) {
    // Input hygiene bug in OUR data: sanitize and retry once, or reject the input.
    const clean = args.map((a) => a.replace(/[\0\r\n]+/g, ''));
    cmdline = composeWin32Cmd(npmBin, clean);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling quoteWin32Arg(arg) where arg contains \0, \r or \n — in practice, a cache directory path, package spec, or model name derived from user input or a repository path that embedded a newline (e.g. a checked-out directory name containing a line break, or pasted config with trailing CRLF inside a value).

Common situations: A malicious or unusual repo path with embedded control characters reaching a spawn on Windows; config values copy-pasted with embedded newlines; programmatic construction of args from untrusted strings. On non-Windows hosts the function is never on the code path, so the same input passes silently.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20). Data as JSON: /api/errors/27c62d7267a2d825. Report an issue: GitHub.