Back to Blog
critical SEVERITY9 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 npm package that allowed attackers to bypass previously shipped security patches. The flaw affected applications using simple-git versions prior to 3.32.3, and was resolved by upgrading to 3.36.0, which introduced a dedicated argument-parsing architecture to properly sanitize untrusted input before it reaches the underlying git process.

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

CVE-2026-28292 is a critical Remote Code Execution (RCE) vulnerability in the simple-git npm library (CWE-78: OS Command Injection) that bypasses earlier security fixes shipped in prior versions. Applications running simple-git 3.30.0 or earlier are at risk of arbitrary command execution if they pass user-controlled input to git operations. The fix is to upgrade simple-git to 3.36.0 (or at minimum 3.32.3), which restructures argument handling into dedicated `@simple-git/argv-parser` and `@simple-git/args-pathspec` sub-packages that enforce strict input validation before arguments reach the underlying git subprocess.

Vulnerability at a Glance

cweCWE-78 (Improper Neutralization of Special Elements used in an OS Command)
fixUpgrade simple-git from 3.30.0 to 3.36.0, which introduces isolated argv-parser and args-pathspec sub-packages with hardened input validation
riskArbitrary command execution on the server hosting the Node.js application
languageJavaScript / Node.js
root causesimple-git 3.30.0 failed to fully sanitize user-controlled arguments passed to git subprocesses, bypassing sanitization added in prior CVE patches
vulnerabilityRemote Code Execution via argument injection bypass

How Remote Code Execution via Security Fix Bypass Happens in Node.js and How to Fix It


Vulnerability at a Glance

Field Detail
CVE CVE-2026-28292
Severity Critical
Package simple-git (npm)
Affected version < 3.32.3 (project was on 3.30.0)
Fixed version 3.36.0
CWE CWE-78 – OS Command Injection
Impact Remote Code Execution

Introduction

The package-lock.json file in the nomacode project pinned simple-git at ^3.22.0, which resolved to version 3.30.0 at install time. That version carries CVE-2026-28292 — a critical Remote Code Execution vulnerability that is particularly dangerous because it is not a brand-new attack class: it is a bypass of security fixes that were already shipped in earlier versions of the library.

Bypass vulnerabilities are some of the most insidious in the ecosystem. Developers who saw previous simple-git CVEs, upgraded, and moved on may believe they are protected. CVE-2026-28292 proves they are not — at least not until version 3.32.3.

The nomacode project uses simple-git alongside express and ws, strongly suggesting it exposes a web interface or WebSocket API that can trigger git operations. If any of those operations accept user-controlled input — a branch name, a commit ref, a path — the application was vulnerable to full remote code execution on the server.


The Vulnerability Explained

What simple-git does

simple-git is a lightweight Node.js wrapper around the git command-line tool. It spawns child processes that call git with arguments constructed from the values you pass to its API. For example:

// Typical usage in a web application
const git = simpleGit('/path/to/repo');

// If branchName comes from an HTTP request parameter...
app.post('/checkout', async (req, res) => {
  const branchName = req.body.branch; // ← user-controlled
  await git.checkout(branchName);     // ← passed to git subprocess
});

When branchName is main, this is perfectly safe. When branchName is something like --upload-pack=touch /tmp/pwned or a crafted pathspec that exploits git's argument parsing, the result is arbitrary command execution.

Why prior fixes were insufficient

simple-git has been patched for argument injection before. The project has a history of CVEs in this class, and each one prompted the maintainers to add sanitization for the specific bypass technique discovered. CVE-2026-28292 represents a new bypass route that circumvented those earlier controls.

The vulnerable version in this project was 3.30.0:

// package-lock.json BEFORE fix
"node_modules/simple-git": {
  "version": "3.30.0",
  "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.30.0.tgz",
  "integrity": "sha512-q6lxyDsCmEal/MEGhP1aVyQ3oxnagGlBDOVSIB4XUVLl1iZh0Pah6ebC9V4xBap/RfgP2WlI8EKs0WS0rMEJHg=="
}

Version 3.30.0 did not include the architectural refactor that properly isolates argument construction from user input. An attacker who could influence any string passed to a simple-git method — a branch name, a tag, a remote URL, a file path — could inject additional git arguments or shell metacharacters that would be forwarded to the underlying git subprocess.

Concrete attack scenario for nomacode

Given that nomacode uses express and ws, consider a WebSocket handler like:

// Hypothetical handler in nomacode
ws.on('message', async (message) => {
  const { action, ref } = JSON.parse(message);
  if (action === 'fetch') {
    await git.fetch('origin', ref); // ref is attacker-controlled
  }
});

With simple-git 3.30.0, a malicious WebSocket client could send:

{
  "action": "fetch",
  "ref": "--upload-pack=id>/tmp/rce_proof origin main"
}

The bypass in CVE-2026-28292 means the sanitization that should have blocked this specific pattern did not. The git fetch subprocess would receive the injected --upload-pack flag, executing the attacker's command (id>/tmp/rce_proof) on the server.

Real-world impact: Full server compromise. The Node.js process running nomacode has filesystem access, environment variables (potentially containing API keys, database credentials), and network access. An RCE at this layer is a complete breach.


The Fix

Upgrading simple-git to 3.36.0

The fix is a version upgrade in both package.json and package-lock.json:

// package-lock.json BEFORE
"simple-git": "^3.22.0"  // resolves to 3.30.0

// package-lock.json AFTER
"simple-git": "^3.36.0"  // resolves to 3.36.0
// node_modules/simple-git BEFORE
{
  "version": "3.30.0",
  "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.30.0.tgz",
  "integrity": "sha512-q6lxyDsCmEal/..."
}

// node_modules/simple-git AFTER
{
  "version": "3.36.0",
  "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.36.0.tgz",
  "integrity": "sha512-cGQjLjK8bxJw4QuYT7gxHw3/Io..."
}

The architectural change: dedicated argument-parsing sub-packages

The most significant aspect of the fix visible in the diff is the introduction of two new sub-packages that did not exist in 3.30.0:

// NEW in the fixed package-lock.json
"node_modules/@simple-git/args-pathspec": {
  "version": "1.0.3",
  "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz",
  "integrity": "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==",
  "license": "MIT"
},
"node_modules/@simple-git/argv-parser": {
  "version": "1.1.1",
  "resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz",
  "integrity": "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==",
  "license": "MIT",
  "dependencies": {
    "@simple-git/args-pathspec": "^1.0.3"
  }
}

This is not just a patch on top of existing code. The maintainers extracted argument parsing into purpose-built, independently versioned packages:

  • @simple-git/argv-parser — handles the construction and validation of command-line arguments passed to git. By isolating this logic, the team can apply strict allow-listing and boundary enforcement in one place, rather than patching individual call sites.
  • @simple-git/args-pathspec — specifically handles pathspec arguments (file paths, globs, refs), which are a historically common injection vector in git tooling. Pathspecs have their own syntax rules and their own injection risks; a dedicated module means those rules are enforced consistently.

Why this matters: Previous patches for similar CVEs in simple-git were applied reactively — a specific bypass technique was found, a specific check was added. The refactor into @simple-git/argv-parser and @simple-git/args-pathspec represents a proactive, structural defense: all arguments flow through validated parsers regardless of which simple-git method is called.

Before vs. after: what changed for developers

No API changes are required in nomacode. The upgrade is entirely in the dependency layer. The same git.checkout(branchName) call now routes through @simple-git/argv-parser, which validates and sanitizes branchName before it ever reaches the subprocess invocation.


Prevention & Best Practices

1. Keep dependencies pinned to exact versions in production

The ^3.22.0 range specifier in package.json allowed npm to install 3.30.0, which was vulnerable. Consider using exact version pins ("simple-git": "3.36.0") in production and automating upgrades through a tool like Dependabot or Renovate.

2. Validate user input before passing it to git operations

Even with a patched library, defense in depth requires validating input at the application layer:

// Example: validate branch names before passing to simple-git
const SAFE_REF_PATTERN = /^[a-zA-Z0-9._\-\/]+$/;

app.post('/checkout', async (req, res) => {
  const branchName = req.body.branch;

  // Reject anything that doesn't look like a valid git ref
  if (!SAFE_REF_PATTERN.test(branchName)) {
    return res.status(400).json({ error: 'Invalid branch name' });
  }

  await git.checkout(branchName);
});

3. Run vulnerability scanners in CI

Trivy detected this vulnerability by comparing the installed version in package-lock.json against its CVE database. Integrate Trivy or a similar scanner (Snyk, npm audit, OWASP Dependency-Check) into your CI pipeline so vulnerable dependencies are caught before they reach production.

# Example GitHub Actions step
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: 'fs'
    scan-ref: '.'
    severity: 'CRITICAL,HIGH'
    exit-code: '1'

4. Treat git library inputs as untrusted

Any string that originates outside your application — HTTP parameters, WebSocket messages, environment variables set by external systems — must be treated as untrusted. This is especially true for inputs that will be passed to subprocess-wrapping libraries like simple-git.

5. Monitor the simple-git security advisory history

simple-git has had multiple CVEs in the argument injection class. Subscribe to GitHub security advisories for this package and review the npm advisory database regularly.

Relevant standards

  • OWASP A03:2021 – Injection: This vulnerability falls squarely in the injection category. The OWASP OS Command Injection Defense Cheat Sheet recommends avoiding shell invocations entirely where possible, and using parameterized APIs when they are necessary.
  • CWE-78: Improper Neutralization of Special Elements used in an OS Command. The mitigation guidance recommends input validation, output encoding, and the principle of least privilege for the process executing commands.

Key Takeaways

  • simple-git 3.30.0 in package-lock.json was the direct source of the risk — the version range ^3.22.0 silently allowed a vulnerable install. Pinning or automating upgrades would have caught this sooner.
  • Bypass vulnerabilities are more dangerous than new CVEs — developers who patched earlier simple-git CVEs may have believed they were safe. CVE-2026-28292 demonstrates that incremental patches on argument injection are fragile; the architectural refactor in 3.36.0 is a more durable fix.
  • The introduction of @simple-git/argv-parser and @simple-git/args-pathspec is the real fix — not just a version bump. These new sub-packages centralize and harden all argument validation in one auditable place.
  • nomacode's use of express and ws means user input can reach git operations over HTTP or WebSocket — any user-facing endpoint that triggers a git call must validate its inputs independently of the library.
  • Trivy caught this at the package-lock.json level — static analysis on the lock file, not just package.json, is essential because lock files reflect what is actually installed.

How Orbis AppSec Detected This

  • Source: User-controlled strings (e.g., branch names, refs, paths) entering the application via HTTP request parameters or WebSocket messages in the nomacode application.
  • Sink: The simple-git library's internal subprocess invocation, where user-controlled strings were assembled into git command arguments without complete sanitization — reachable through any git.*() call in the application code.
  • Missing control: The installed version of simple-git (3.30.0) lacked the @simple-git/argv-parser and @simple-git/args-pathspec sub-packages that enforce strict argument validation; earlier sanitization logic contained a bypass that CVE-2026-28292 exploits.
  • CWE: CWE-78 — Improper Neutralization of Special Elements used in an OS Command (OS Command Injection).
  • Fix: Upgraded simple-git from 3.30.0 to 3.36.0 in package-lock.json and updated the version range in package.json from ^3.22.0 to ^3.36.0, introducing the hardened @simple-git/argv-parser architecture.

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 stark reminder that patching a vulnerability class once is not enough. simple-git has been through this rodeo before — and each time, the patches were applied to specific bypass techniques rather than the underlying architectural problem. Version 3.36.0 changes that by extracting argument handling into dedicated, independently versioned packages (@simple-git/argv-parser, @simple-git/args-pathspec) that enforce validation at the boundary between application code and the git subprocess.

For the nomacode project, the upgrade from 3.30.0 to 3.36.0 closes the RCE vector. But the broader lesson is architectural: any Node.js application that passes user-influenced data to a subprocess-wrapping library must treat that data as untrusted, validate it at the application layer, and keep the underlying library current. Dependency scanning in CI — as demonstrated by Trivy's detection of this exact version in package-lock.json — is the safety net that catches what code review misses.


References

Frequently Asked Questions

What is Remote Code Execution via security fix bypass?

It is an attack where a vulnerability persists despite a prior patch because the fix was incomplete — an attacker crafts input that follows a slightly different code path that the original patch did not cover, ultimately still reaching a dangerous call site such as a shell subprocess.

How do you prevent argument injection in Node.js git libraries?

Always validate and sanitize any user-controlled strings before passing them to git operations, pin dependencies to patched versions, and prefer libraries that use dedicated argument-parsing modules rather than constructing command strings manually.

What CWE is this Remote Code Execution vulnerability?

CWE-78: Improper Neutralization of Special Elements used in an OS Command (OS Command Injection), because the root cause is unsanitized input reaching a subprocess invocation.

Is upgrading the package enough to prevent this vulnerability?

Yes, upgrading simple-git to 3.36.0 (or 3.32.3 at minimum) closes the specific bypass. However, you should also audit all call sites that pass user-controlled data to simple-git methods and apply input validation as a defense-in-depth measure.

Can static analysis detect this vulnerability?

Yes. Trivy flagged CVE-2026-28292 by matching the installed version of simple-git in package-lock.json against its vulnerability database. Tools like Semgrep can additionally trace tainted data flows from HTTP inputs to git subprocess calls.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #14

Related Articles

high

How Command Injection happens in Node.js child_process calls and how to fix it

A high-severity command injection vulnerability was discovered in `Config/QuickAdd/git-add-new-origin-branch.js`, where user-supplied branch names were interpolated directly into a shell command string passed to `child_process.exec()`. The fix replaces the shell-interpolated `exec()` call with `execFile()`, passing arguments as a discrete array and eliminating the shell entirely. This proactive hardening removes an exploit primitive that could have been chained with other weaknesses to achieve a

high

How Command Injection happens in PHP shell execution and how to fix it

A command injection vulnerability in `sitrecServer/windProxy.php` allowed user-controlled input to reach a shell command without proper sanitization, creating a remote code execution risk. The `$cycleHour` parameter was passed directly as a format integer (`%d`) into a `sprintf`-built shell command, bypassing the `escapeshellarg()` protection applied to all other arguments. The fix casts `$cycleHour` to an integer and wraps it with `escapeshellarg()`, closing the injection path entirely.

critical

How Command Injection happens in Node.js shell-quote and how to fix it

A critical command injection vulnerability (CVE-2026-9277) in the `shell-quote` npm package versions prior to 1.8.4 allowed attackers to execute arbitrary code by injecting unescaped line terminators into shell arguments. The fix upgrades `shell-quote` from 1.8.2 to 1.9.0 and pins the dependency across `package.json`, `package-lock.json`, and `yarn.lock` to ensure no transitive dependency can pull in the vulnerable version.

high

How Command Injection via Unescaped Line Terminators Happens in Node.js and How to Fix It

The `shell-quote` package (versions prior to 1.9.0) contained a critical command injection vulnerability where unescaped line terminators in shell arguments could be exploited to inject arbitrary commands. This vulnerability was discovered in the docs-site dependency tree and fixed by upgrading to version 1.9.0, which properly escapes line terminators to prevent attackers from breaking out of quoted arguments and executing malicious shell commands.

critical

How Command Injection via Unescaped Line Terminators Happens in Node.js and How to Fix It

A critical command injection vulnerability (CVE-2026-9277) was discovered in the shell-quote npm package version 1.8.3, where unescaped line terminators could allow attackers to execute arbitrary code. This fix upgrades shell-quote to version 1.9.0 using npm overrides to ensure all instances in the dependency tree are patched, eliminating the attack vector across the entire application.

high

How Command Injection happens in PHP and how to fix it

A high-severity command injection vulnerability was discovered in `lib/Controller/Helper.php` where the `corruptline()` method used `exec()` to run sed and awk commands with user-controlled input. The fix replaced all shell command execution with native PHP file operations using `SplFileObject`, eliminating the command injection attack surface entirely.