CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-22864
8.10.07%

Case-Insensitive Nightmares: Deno's Command Injection Bypass

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 17, 2026·7 min read·8 visits

PoC Available

Executive Summary (TL;DR)

Deno tried to stop command injection by blocking '.bat' and '.cmd' files. They forgot Windows is case-insensitive. By using '.BAT', attackers can trick Deno into executing batch files with unsafe arguments, leading to RCE.

A high-severity command injection vulnerability in Deno runtime on Windows, caused by an incomplete fix for the 'BatBadBut' class of bugs. Attackers could bypass extension blocklists by simply changing the case of the file extension (e.g., '.BAT' instead of '.bat'), allowing arbitrary code execution via argument injection.

The Hook: Secure by Default?

Deno markets itself as the "secure by default" runtime for JavaScript and TypeScript. Unlike Node.js, it requires explicit permissions to access the network, file system, or environment. It’s a bold claim, and for the most part, the Deno team does a stellar job upholding it. But security is a chain, and that chain is often anchored to the underlying operating system. When that OS is Windows, things get weird.

This vulnerability, CVE-2026-22864, is a classic example of "developer vs. legacy compatibility." It involves the terrifyingly complex way Windows creates processes and parses arguments. Specifically, it touches on the "BatBadBut" class of vulnerabilities—a recurring nightmare where programming languages fail to account for the fact that cmd.exe handles arguments like a drunk juggler.

The core issue here isn't memory corruption or a buffer overflow. It's a logic error born from a simple misunderstanding: assuming that file.bat and file.BAT are different things. In the Unix world, they are. In the Windows world, they are identical twins, but Deno only recognized one of them as a threat.

The Flaw: A Case of Mistaken Identity

To understand this bug, you have to understand CreateProcess on Windows. When you spawn a process pointing to a .bat or .cmd file, Windows implicitly calls cmd.exe /c your_script.bat. The problem is that cmd.exe has its own parsing rules for arguments that are completely separate from the standard C runtime parsing. Characters like &, |, and ^ are interpreted as control operators before the script even sees them.

To mitigate this, runtimes like Python, Rust, and Node have had to implement complex escaping logic. Deno, in an earlier attempt to patch this, decided to simply block the execution of batch files when certain dangerous APIs were used, or at least sanitize them heavily. They implemented a check: if the file extension is .bat or .cmd, apply strict security measures.

Here lies the flaw. The check was case-sensitive. The code looked for bat (lowercase). But the Windows filesystem (NTFS) and the process loader don't care about case. If an attacker passes payload.BAT or payload.cMd, the blocklist sees a "safe" extension, but Windows sees a batch file. The runtime hands the arguments over to the OS without the special "batch file escaping," and cmd.exe happily executes any injected commands found in those arguments.

The Code: The Smoking Gun

Let's look at the Rust code responsible for this. In ext/process/lib.rs (prior to the fix), the logic was painfully simple. It treated the extension as a raw string and compared it against lowercase literals. It's the kind of code you write at 3 AM thinking, "Yeah, that covers it."

The Vulnerable Code:

// Vulnerable logic snippet
if ext_str.eq("bat") || ext_str.eq("cmd") {
    // Apply security blocking or special escaping
    return Err(PermissionDenied);
}

This is like locking the front door but leaving the window wide open because the burglar didn't ring the doorbell. The fix, introduced in version 2.5.6 (Commit 1b07f0295dd91f393966f953099ee90505a97e50), had to implement a robust, case-insensitive check. The Deno team ended up porting the logic directly from the Rust standard library (which had already learned this lesson the hard way in CVE-2024-24576).

The Fixed Code:

// Robust, case-insensitive check using bitwise matching
let has_bat_extension = |program: &[u16]| {
    matches!(
      program.len().checked_sub(4).and_then(|i| program.get(i..)),
      // Matches .bat, .BAT, .BaT, .cmd, .CMD, etc.
      Some(
        [46, 98 | 66, 97 | 65, 116 | 84] | [46, 99 | 67, 109 | 77, 100 | 68]
      )
    )
};

Notice the bitwise | operations? That handles b (98) or B (66), a (97) or A (65), ensuring that any variation of "bat" is caught. They also updated deno_path_util to strip trailing dots and spaces, because file.bat. is also a valid batch file on Windows. It's a game of whack-a-mole.

The Exploit: Bypassing the Guard

Exploiting this is trivially easy if you can control the filename and arguments passed to Deno.Command. Imagine a scenario where a Deno application runs a helper script specified by a user or configuration file. If the attacker can point that execution to a file named with uppercase letters, they win.

Here is a functional Proof of Concept (PoC) for a vulnerable Deno version running on Windows:

// victim.ts
// Run with: deno run --allow-run victim.ts
 
// The attacker provides a file with an uppercase extension
const maliciousFile = "test_script.BAT";
 
// The attacker injects a command via arguments
// The '&' is a command separator in cmd.exe
const cmd = new Deno.Command(maliciousFile, {
  args: ["& calc.exe"],
});
 
console.log("[+] Attempting to spawn process...");
const output = await cmd.output();

The Execution Flow:

  1. Deno checks test_script.BAT. Does it end in .bat? No. It ends in .BAT.
  2. Deno assumes it is a regular executable (like .exe) and does not apply the special batch-file escaping rules.
  3. Deno calls Windows CreateProcess.
  4. Windows sees .BAT, invokes cmd.exe /c test_script.BAT & calc.exe.
  5. cmd.exe runs the script, hits the &, and then executes calc.exe.

The Impact: Why This Matters

While the requirement to control the filename limits the surface area slightly, the impact is catastrophic when the condition is met. This is a classic Remote Code Execution (RCE) vector. If a developer uses Deno.Command to execute a user-supplied path—perhaps in a CI/CD runner, a build tool, or a server-side utility wrapper—the attacker gains full control over the Deno process's privileges.

Because Deno is often used to build modern CLI tools and backend services, this bypass effectively neutralizes the sandbox protections regarding subprocess execution. Even if you have tight --allow-run permissions, if you allow running a specific directory, and the attacker can drop a .BAT file there (or use an existing one), they can break out of the intended logic chain.

It is also a stark reminder that "sanitizing input" is impossible if you don't understand how the underlying system interprets that input. The disparity between the application's view of a file (.BAT != .bat) and the OS's view (.BAT == .bat) is a fertile ground for high-severity vulnerabilities.

The Fix: Remediation and Lessons

The remediation is straightforward: Update to Deno v2.5.6. The patch aligns Deno's behavior with the realities of the Windows API. It now explicitly invokes %COMSPEC% (cmd.exe) with proper flags (/d /s /c) and robust argument escaping whenever any case-variation of a batch extension is detected.

For Developers:

  1. Never assume case-sensitivity. If you are writing cross-platform code, remember that Windows and macOS (usually) are case-insensitive, while Linux is case-sensitive.
  2. Avoid Batch Files. In 2026, there is almost no reason to rely on .bat files for critical infrastructure. Use PowerShell, or better yet, write the logic in JavaScript/TypeScript directly within Deno.
  3. Use allow-lists. If you must spawn subprocesses, validate the entire path against a strict allow-list, not just the extension. Ensure the file path is canonicalized before checking.

This vulnerability serves as a humorous, dark reminder: You might be writing modern, cutting-edge Rust and TypeScript, but underneath it all, you're still negotiating with MS-DOS ghosts from the 1980s.

Official Patches

DenoDeno v2.5.6 Release Notes

Fix Analysis (1)

Technical Appendix

CVSS Score
8.1/ 10
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
EPSS Probability
0.07%
Top 79% most exploited

Affected Systems

Deno runtime on Windows

Affected Versions Detail

Product
Affected Versions
Fixed Version
Deno
Deno Land
< 2.5.62.5.6
AttributeDetail
CWE IDCWE-77
Attack VectorNetwork (Context Dependent)
CVSS Score8.1 (High)
ImpactRemote Code Execution (RCE)
EPSS Score0.00067
PlatformWindows

MITRE ATT&CK Mapping

T1059.003Windows Command Shell
Execution
T1202Indirect Command Execution
Defense Evasion
CWE-77
Improper Neutralization of Special Elements used in a Command ('Command Injection')

Known Exploits & Detection

Internal ResearchPoC demonstrating argument injection using uppercase .BAT extension

Vulnerability Timeline

Fix committed to Deno repository
2025-10-28
Deno v2.5.6 released
2025-10-29
Public Disclosure (CVE-2026-22864)
2026-01-15

References & Sources

  • [1]GHSA-m3c4-prhw-mrx6: Command Injection Bypass on Windows
  • [2]BatBadBut: You Can't Securely Execute Commands on Windows
Related Vulnerabilities
CVE-2024-24576

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.