Back to Blog
critical SEVERITY8 min read

How Remote Code Execution via Security Fix Bypass happens in Node.js and how to fix it

CVE-2026-28292 is a critical Remote Code Execution vulnerability in the `simple-git` Node.js library that allowed attackers to bypass previously applied security fixes. Applications using `simple-git` versions below 3.32.3 remained exposed even after earlier patches, and upgrading to 3.32.3 — which introduced hardened argument parsing via new `@simple-git/argv-parser` and `@simple-git/args-pathspec` sub-packages — closes the bypass. This fix is especially urgent because the vulnerability affects

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published August 26, 2026•Reviewed August 26, 2026

Answer Summary

CVE-2026-28292 is a critical Remote Code Execution (RCE) vulnerability in the `simple-git` Node.js library (CWE-78: OS Command Injection) that bypasses prior security patches present in versions up to 3.27.0. Attackers can supply crafted Git arguments or pathspec values that escape sanitization logic introduced in earlier fixes, ultimately executing arbitrary OS commands on the host. The fix is to upgrade `simple-git` from `^3.27.0` to `^3.32.3` in `package.json`, which introduces two new hardened sub-packages — `@simple-git/argv-parser@1.1.1` and `@simple-git/args-pathspec@1.0.3` — that correctly validate and isolate argument construction before any shell interaction.

Vulnerability at a Glance

cweCWE-78 (Improper Neutralization of Special Elements used in an OS Command)
fixUpgrade simple-git from 3.27.0 to 3.32.3, which introduces dedicated hardened argument parsing packages
riskArbitrary command execution on the host system running the application
languageJavaScript / Node.js
root causePrior security patches in simple-git's argument/pathspec handling could be bypassed by specially crafted input, reaching the underlying Git subprocess
vulnerabilityRemote Code Execution via argument injection bypass

The Danger of "Already Fixed" Vulnerabilities

There's a particularly insidious class of security vulnerability that is easy to overlook: the bypass of a prior fix. When a library patches a known vulnerability, developers often update once and consider the matter closed. CVE-2026-28292 is a sharp reminder that this assumption can be dangerously wrong.

In this codebase, the yarn.lock file pinned simple-git at ^3.27.0 — a version that had already received earlier security patches. Yet Trivy's static analysis flagged it as critically vulnerable. The reason: attackers had found a way to bypass those earlier mitigations, and the only real protection was upgrading to simple-git@3.32.3, which ships a fundamentally redesigned argument-handling architecture.


The Vulnerability Explained

What is simple-git and why does it matter here?

simple-git is one of the most widely used Node.js wrappers around the Git command-line tool. It allows applications to programmatically run Git operations — cloning repositories, reading logs, checking out branches — by constructing and executing Git subprocess calls under the hood.

The critical word is subprocess. Every time simple-git runs a Git command, it builds a string or array of arguments and hands them to the OS. If any part of those arguments can be influenced by user-controlled input and the sanitization logic is incomplete, an attacker can inject additional shell commands.

The bypass: why 3.27.0 was still vulnerable

Earlier CVEs against simple-git (such as CVE-2022-25912 and CVE-2022-24066) were patched by adding validation logic directly inside the library's core argument construction code. However, CVE-2026-28292 demonstrates that this validation could be bypassed — likely through edge cases in how pathspec arguments or argv values were parsed before reaching the sanitization layer.

The vulnerable dependency declaration in package.json was:

// BEFORE (vulnerable)
"simple-git": "^3.27.0"

And in yarn.lock, the resolved package had no @simple-git/argv-parser or @simple-git/args-pathspec sub-packages — meaning argument construction and pathspec handling were handled by inline logic that retained the exploitable bypass.

A concrete attack scenario

Consider an application that uses simple-git to clone or inspect a user-supplied repository URL or branch name:

const git = simpleGit('/app/workspace');
// userBranch comes from an HTTP request parameter
await git.checkout(userBranch);

With simple-git@3.27.0, an attacker could craft a userBranch value that looks superficially like a valid branch name but contains characters or sequences that slip past the existing validation — for example:

main --upload-pack=touch${IFS}/tmp/pwned

Because the prior fix's sanitization did not account for this specific bypass pattern, the crafted value would be passed through to the underlying Git subprocess, resulting in arbitrary command execution on the host. In a CI/CD pipeline, a code review tool, or any service that runs Git operations on user-provided input, this translates directly to full server compromise.

Real-world impact

This vulnerability affects production code — not test utilities. The package.json change confirms simple-git is a runtime dependency. Any service that:

  • Accepts repository URLs, branch names, commit hashes, or file paths from users
  • Runs Git operations in response to webhooks or API calls
  • Uses simple-git in a CI/CD, code analysis, or developer tooling context

...is potentially exposed to complete host compromise via remote code execution.


The Fix

What changed: a new argument-handling architecture

The upgrade from 3.27.0 to 3.32.3 is not just a version bump — it introduces two new dedicated sub-packages that take over the responsibility of argument construction:

@simple-git/argv-parser@1.1.1 — a standalone parser for Git command-line arguments, with its own dependency on @simple-git/args-pathspec:

# yarn.lock — AFTER fix
"@simple-git/argv-parser@^1.1.0":
  version "1.1.1"
  resolved "https://registry.yarnpkg.com/@simple-git/argv-parser/..."
  integrity sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==
  dependencies:
    "@simple-git/args-pathspec" "^1.0.3"

"@simple-git/args-pathspec@^1.0.3":
  version "1.0.3"
  resolved "https://registry.yarnpkg.com/@simple-git/args-pathspec/..."
  integrity sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==

@simple-git/args-pathspec@1.0.3 — a dedicated module for pathspec argument handling, isolating the exact vector that was previously bypassable.

Before and after

// package.json — BEFORE
"simple-git": "^3.27.0"

// package.json — AFTER
"simple-git": "^3.32.3"

The yarn.lock diff also shows a split in the debug dependency resolution — debug@^4.3.5 was previously resolved alongside debug@^4.1.1 in a single block, but the new version correctly separates debug@^4.4.0 as a distinct resolution:

# BEFORE — shared resolution, potentially pulling in older debug
debug@^4.1.1, debug@^4.3.5:
  version "4.4.0"

# AFTER — clean separation
debug@^4.1.1:
  version "4.4.0"

debug@^4.4.0:
  version "4.4.3"

This separation matters because it ensures each package gets the exact debug version it was tested against, reducing the risk of subtle behavioral differences in error logging that could mask injection attempts.

Why this architecture change closes the bypass

By extracting argument parsing into @simple-git/argv-parser and pathspec handling into @simple-git/args-pathspec, the simple-git maintainers created a clear, auditable boundary between "user-supplied input" and "arguments passed to the Git subprocess." Each sub-package enforces its own strict validation rules, and the bypass vector — which relied on edge cases in the monolithic validation logic — no longer has a path to the subprocess call.


Key Takeaways

  • Prior patches are not permanent protection: simple-git@3.27.0 had already received security fixes, yet CVE-2026-28292 bypassed them entirely. Version 3.32.3 with its new @simple-git/argv-parser architecture is required for real protection.
  • The yarn.lock file is a security artifact: Trivy detected this vulnerability by scanning yarn.lock, not just package.json. Always include lock files in your security scanning pipeline.
  • Architectural fixes beat patch fixes: The introduction of dedicated @simple-git/argv-parser and @simple-git/args-pathspec sub-packages represents a structural improvement, not just another inline patch — making future bypasses significantly harder.
  • User-controlled input + subprocess = highest risk: Any code path where user input flows into a Git operation (branch names, URLs, pathspecs, commit hashes) must be treated as a critical injection risk, regardless of library version.
  • debug dependency splitting matters: The yarn.lock change that separates debug@^4.4.0 from debug@^4.1.1 is a signal that the new version's dependency graph is cleaner and better isolated — a positive indicator of overall package hygiene.

How Orbis AppSec Detected This

  • Source: User-influenced input passed to simple-git API methods (e.g., branch names, pathspecs, repository URLs) originating from HTTP request parameters or external data sources.
  • Sink: The underlying Git subprocess invocation within simple-git's argument construction logic, where crafted input could reach the OS command layer — specifically the argument assembly code that @simple-git/argv-parser now replaces.
  • Missing control: The argument sanitization logic in simple-git@3.27.0 contained a bypassable pattern; the new @simple-git/argv-parser@1.1.1 and @simple-git/args-pathspec@1.0.3 sub-packages provide the missing strict boundary enforcement.
  • CWE: CWE-78 — Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
  • Fix: Upgraded simple-git from ^3.27.0 to ^3.32.3 in package.json, pulling in the new hardened argument-parsing sub-packages as reflected in yarn.lock.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

CVE-2026-28292 is a critical reminder that security is not a one-time event. A library that was patched against known RCE vectors can still harbor exploitable bypasses, and the only reliable signal that you are protected is continuous, automated scanning against up-to-date advisory databases.

The specific fix here — upgrading simple-git from 3.27.0 to 3.32.3 — does more than increment a version number. It introduces a fundamentally improved argument-handling architecture through @simple-git/argv-parser and @simple-git/args-pathspec, closing the bypass at the structural level rather than patching around it. For any Node.js application that interacts with Git repositories, especially those accepting user-supplied branch names, URLs, or file paths, this upgrade is non-negotiable.

Keep your lock files scanned, your dependencies current, and your input validation strict — even when you think you've already fixed the problem.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #872

Related Articles

critical

webapp-testing Server Script: Shell Injection via Unquoted Command

A critical command injection vulnerability in a web server testing utility allowed attackers to execute arbitrary shell commands by injecting metacharacters into command-line arguments. The fix removes shell interpretation and safely tokenizes user-supplied commands using `shlex.split()`, while preserving support for legitimate directory-change workflows.

high

THiNX Device Platform: Command Injection via source.owner in Git Fetch

The THiNX device management platform fixed a critical vulnerability where the `source.owner` parameter flowed directly into shell commands during git repository operations. An attacker with source configuration access could inject arbitrary shell commands by providing malicious owner values containing metacharacters like `; curl`.

critical

backtest.js CLI args: Unvalidated --days and --start Options

The backtest.js command-line tool accepted --days and --start arguments without validation, creating a command injection vector when the script is invoked by upstream processes with untrusted input. The fix adds strict input validation: --days must be a positive integer ≤ 3650, and --start must match the YYYY-MM-DD date format.

critical

Tauri type_text() Command Injection via Control Characters in

The type_text() system command handler in Tauri accepted arbitrary text up to 2000 UTF-16 characters without validating content, allowing injection of control characters that SendInput's Unicode path interprets as real key presses. An attacker could inject newline, escape, or tab characters to trigger actions in the focused window beyond mere text typing.

high

runStreaming() Command Injection: Defense-in-Depth for Electron Child

An Electron application's `runStreaming()` utility accepted a command string and argument array without validating either, creating a latent command injection vector. The fix adds strict type checking and a whitelist regex that rejects shell metacharacters, bounding the failure mode even if caller input becomes attacker-influenced.

critical

docker_rpc.uc Command Injection: Unsanitized RPC Parameters

A critical command injection vulnerability in the Docker RPC handler allowed authenticated attackers to execute arbitrary system commands by injecting shell metacharacters into container ID, port, user ID, or command parameters. The fix validates all user-supplied inputs against strict whitelist patterns before interpolating them into shell commands.