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-gitin 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.
Prevention & Best Practices
1. Treat dependency upgrades as security events, not routine maintenance
CVE-2026-28292 exists specifically because a prior fix was incomplete. This means that simply having patched once is not sufficient. Implement automated dependency scanning (Trivy, Snyk, Dependabot) that continuously monitors your yarn.lock and package.json against updated advisory databases.
2. Never pass unsanitized user input to Git operations
Even with a fully patched simple-git, the principle of least privilege applies. Validate and allowlist any user-supplied values before passing them to Git operations:
// Dangerous — user input directly to git
await git.checkout(req.body.branch);
// Safer — validate against known-safe pattern first
const SAFE_BRANCH = /^[a-zA-Z0-9._\-\/]{1,200}$/;
if (!SAFE_BRANCH.test(req.body.branch)) {
throw new Error('Invalid branch name');
}
await git.checkout(req.body.branch);
3. Use software composition analysis (SCA) in CI/CD
The Trivy rule CVE-2026-28292 caught this by matching the version range in yarn.lock. Integrate SCA scanning as a required CI step so vulnerable dependencies are blocked 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. Lock and audit transitive dependencies
The new @simple-git/argv-parser and @simple-git/args-pathspec sub-packages appear in yarn.lock as explicit resolved entries. Regularly audit your lock file — not just your direct dependencies — for unexpected or vulnerable transitive packages.
5. Relevant security standards
- OWASP A03:2021 – Injection: This vulnerability is a direct instance of injection via OS command construction.
- CWE-78: Improper Neutralization of Special Elements used in an OS Command.
- OWASP Dependency-Check / OWASP SCA guidance: Always run composition analysis against both
package.jsonand lock files.
Key Takeaways
- Prior patches are not permanent protection:
simple-git@3.27.0had already received security fixes, yet CVE-2026-28292 bypassed them entirely. Version3.32.3with its new@simple-git/argv-parserarchitecture is required for real protection. - The
yarn.lockfile is a security artifact: Trivy detected this vulnerability by scanningyarn.lock, not justpackage.json. Always include lock files in your security scanning pipeline. - Architectural fixes beat patch fixes: The introduction of dedicated
@simple-git/argv-parserand@simple-git/args-pathspecsub-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.
debugdependency splitting matters: Theyarn.lockchange that separatesdebug@^4.4.0fromdebug@^4.1.1is 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-gitAPI 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-parsernow replaces. - Missing control: The argument sanitization logic in
simple-git@3.27.0contained a bypassable pattern; the new@simple-git/argv-parser@1.1.1and@simple-git/args-pathspec@1.0.3sub-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-gitfrom^3.27.0to^3.32.3inpackage.json, pulling in the new hardened argument-parsing sub-packages as reflected inyarn.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.