← Notes

simple-git RCE: Bypassing the Blocklist via core.sshCommand Injection

Overview

ReportGHSA-p488-6wq4-vh78 (not publicly accessible)
Upstream releasesimple-git 3.33.0 release notes
Packagesimple-git (npm)
AffectedApplications that pass attacker-controlled values into simple-git's custom argument surface
Fixedsimple-git 3.33.0
ClassCWE-88 (Argument Injection)
CVSS v3.19.8 (Critical) -- AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Found byLukas 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