{"record":{"id":"27c62d7267a2d825","repo":"abhigyanpatwari/GitNexus","slug":"argument-contains-nul-cr-lf-unsafe-for-the-window","errorCode":null,"errorMessage":"argument contains NUL/CR/LF, unsafe for the Windows shell: ${JSON.stringify(arg)}","messagePattern":"argument contains NUL/CR/LF, unsafe for the Windows shell: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/embeddings/runtime-install.ts","lineNumber":308,"sourceCode":" * Quote a single argument for the Windows `cmd.exe` shell (#2372). npm is a\n * `.cmd` shim, so the spawn must go through a shell (EINVAL otherwise since\n * CVE-2024-27980), and Node does NOT escape args under `shell: true` — a spaced\n * `--prefix` path splits, and cmd eats the `^` in `@pkg@^1.0.0` semver ranges.\n *\n * Rules (validated against Node source, MS cmd/CRT docs, BatBadBut, Rust std):\n * reject NUL/CR/LF and embedded `\"` (both unrepresentable/unsafe at the cmd\n * layer, and `\"` is illegal in Windows paths and npm specs); wrap in double\n * quotes when empty or containing whitespace/metacharacters; double the trailing\n * backslash run so the added closing quote is not itself escaped (`C:\\` →\n * `\"C:\\\\\"`). `^` is literal inside cmd double quotes across all three parse\n * layers (cmd `/c` → npm.cmd's `%*` re-parse → node CRT argv). Two documented\n * ceilings quoting can't close: a defined `%VAR%` expands once at the first cmd\n * parse, and `!` expands only under registry-enabled delayed expansion — both\n * are the env-var owner's trust, out of the malicious-repo threat model.\n */\nexport const quoteWin32Arg = (arg: string): string => {\n  if (/[\\0\\r\\n]/.test(arg)) {\n    throw new Error(\n      `argument contains NUL/CR/LF, unsafe for the Windows shell: ${JSON.stringify(arg)}`,\n    );\n  }\n  if (arg.includes('\"')) {\n    throw new Error(\n      `argument contains a double quote, unsafe for the Windows shell: ${JSON.stringify(arg)}`,\n    );\n  }\n  if (arg !== '' && !WIN32_NEEDS_QUOTING.test(arg)) return arg;\n  const trailingBackslashes = /\\\\*$/.exec(arg)?.[0].length ?? 0;\n  return `\"${arg}${'\\\\'.repeat(trailingBackslashes)}\"`;\n};\n\n/**\n * Compose a full `cmd.exe` command line: the command stays unquoted (so\n * PATH/PATHEXT resolves a bare name or `.cmd` shim), args are individually\n * quoted. Passing this as spawn's first (only) string argument — no args array\n * — yields a byte-identical `cmd.exe /d /s /c \"…\"` line while avoiding DEP0190","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/core/embeddings/runtime-install.ts#L290-L326","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["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).","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.","If you control the spawn, prefer spawn(file, [args]) with an args array (no shell) instead of composing a cmd.exe command line.","Do not attempt to work around it by encoding the newline — the guard exists because no cmd.exe quoting can make these bytes safe."],"exampleFix":"// before\nconst cmdline = composeWin32Cmd('npm', [rawPath]); // rawPath contains \"repo\\nname\"\n// -> Error: argument contains NUL/CR/LF, unsafe for the Windows shell: \"repo\\nname\"\n\n// after\nif (/[\\0\\r\\n]/.test(rawPath)) throw new Error(`invalid path: ${JSON.stringify(rawPath)}`);\nconst cleaned = rawPath.replace(/[\\0\\r\\n]+/g, '');\nconst cmdline = composeWin32Cmd('npm', [cleaned]);","handlingStrategy":"validation","validationCode":"// Reject control characters before any Windows command-line composition.\nexport function assertSafeWin32Arg(arg: string): void {\n  if (/[\\0\\r\\n]/.test(arg)) {\n    throw new Error(\n      `unsafe argument (NUL/CR/LF): ${JSON.stringify(arg)} — sanitize the source value`,\n    );\n  }\n}\n\nassertSafeWin32Arg(cacheDir);\nassertSafeWin32Arg(pkgSpec);","typeGuard":"export const isShellSafeArg = (arg: string): boolean =>\n  arg.length > 0 && ![...arg].some((c) => c.charCodeAt(0) < 32);","tryCatchPattern":"try {\n  cmdline = composeWin32Cmd(npmBin, args);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('NUL/CR/LF')) {\n    // Input hygiene bug in OUR data: sanitize and retry once, or reject the input.\n    const clean = args.map((a) => a.replace(/[\\0\\r\\n]+/g, ''));\n    cmdline = composeWin32Cmd(npmBin, clean);\n  } else {\n    throw err;\n  }\n}","preventionTips":["Validate external input at the trust boundary: reject paths/specs containing control characters the moment you receive them.","Prefer spawn(file, argsArray) with shell disabled over composing cmd.exe strings — no quoting round-trip, no injection surface.","Trim CRLF from config values when parsing env/file config, especially values pasted from Windows editors.","Add a unit test feeding \\n and \\r inside paths to your spawn wrapper to prove the guard fires."],"tags":["windows","shell","security","injection","validation"],"backgroundTag":"shell-command-injection","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}