Search
Search is not available in local development.
Run npx pagefind --site __site after building to enable it.
JLSEC-2026-656 High 8.1

Deno: Command Injection via spawnSync & spawn on Windows

JLSEC Published
Modified
Severity
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
Affected Packages
Deno_jll < 2.8.1+0
Aliases / Upstream
CVE-2026-49402 GHSA-7xh3-mhg9-jcw8 EUVD-2026-38546

Summary

Deno's node:child_process implementation provided an escapeShellArg() helper used when callers passed shell: true to spawn / spawnSync / exec and friends. On Windows, the helper failed to quote arguments that contained cmd.exe metacharacters such as &, |, <, >, ^, !, (, ), and did not neutralize % (which cmd.exe expands even inside double-quoted strings). An attacker who controlled any portion of an argument passed to such a call could inject arbitrary additional commands into the spawned cmd.exe invocation.

This was the Windows counterpart to CVE-2026-27190, which fixed the same class of bug in the Unix branch of escapeShellArg.

Details

On Windows, child_process with shell: true ran the command via cmd.exe /d /s /c "<command line>". Deno assembled that command line by joining the program name and each argument through escapeShellArg().

The vulnerable check was:

// If no special characters, return as-is
if (!/[\s"\\]/.test(arg)) {
  return arg;
}

The regex covered only whitespace, double-quote, and backslash. Any argument containing cmd.exe-significant characters but none of those three was returned unquoted and therefore interpreted by the shell. The most straightforward exploit chained commands with &:

import { spawnSync } from "node:child_process";

spawnSync("echo", ["test&calc.exe"], { shell: true, encoding: "utf-8" });

The reporter confirmed this launched calc.exe on Windows 11 with Deno 2.7.5. The same shape worked for |, <, >, ^, !, (, and ).

A secondary defect existed even when arguments were quoted: cmd.exe expands %FOO% environment-variable references inside double-quoted strings. Without either doubling % or rejecting it, an argument like "%USERPROFILE%" leaked environment data into the command line.

Proof of concept

From the report, run on Windows with Deno < 2.7.10:

import { spawnSync } from "node:child_process";

const maliciousInput = "test&calc.exe";
const result = spawnSync("echo", [maliciousInput], {
  shell: true,
  encoding: "utf-8",
});
console.log(result);

Observed: calc.exe launched as a side effect of the echo call.

Impact

Any Deno program on Windows that called child_process.spawn / spawnSync / exec (or any shell helper that funneled through escapeShellArg) with shell: true and incorporated untrusted input into an argument was exposed to arbitrary command execution in the context of the Deno process. The CVSS vector treated this as network-reachable / high-complexity because the typical exposure path was a Deno service accepting external input and forwarding it to a shelled-out subprocess.

Not affected:

  • Calls without shell: true (the default), which executed the program directly via CreateProcess without cmd.exe interpretation.

  • Unix platforms, which used the single-quote branch of escapeShellArg and were already fixed under CVE-2026-27190.

  • Callers that built command strings themselves and passed them as a single string with shell: true — those were the caller's responsibility and were never sanitized by Deno.

Workarounds

Users on unpatched versions could mitigate by:

  • Avoiding shell: true in node:child_process calls on Windows.

  • Building the argv directly and invoking the program without a shell.

  • Filtering or rejecting any externally-supplied argument values that contained cmd.exe metacharacters (& | < > ^ ! ( ) %) before passing them to spawn / spawnSync / exec.

References