GuardVibe
News
· 6 min read

simple-git's Guard Falls Four Ways; proxy-addr Trusts Every Client

simple-git's unsafe-option guard is bypassed four ways to command execution; proxy-addr lets any client fake req.ip; Tinypool, Seroval and vm2 also patch.

Four simple-git advisories published on October 5 show that its built-in guard against dangerous git options can be bypassed in four separate ways, and each one ends in command execution. The same day brought a proxy-addr flaw that lets any client fake req.ip in Express, prototype-pollution-to-RCE gadgets in Tinypool and Seroval, and a second batch of vm2 sandbox escapes that only 3.12.2 fully closes.

What shipped

simple-git: four ways around blockUnsafeOperationsPlugin

  • Package: simple-git (about 15M weekly downloads), plus @simple-git/argv-parser
  • Affected / fixed: trailer.<token>.cmd bypass in 3.15.0 through 4.0.0, fixed in 4.0.1 (GHSA-x6jw-m9v5-85vh, critical). include.path config loading (GHSA-g4wm-2vf7-vfgr, CVSS 8.1) and abbreviated --receive-p options (GHSA-858h-whjf-mvg5, CVSS 8.1) affect versions up to 3.36.0 and are fixed in 4.0.0. The parser ignored the VISUAL editor variable in @simple-git/argv-parser before 2.0.1 (GHSA-v5rq-49vh-5v5c, critical).
  • Impact: if attacker-influenced text reaches customArgs, -c config or SimpleGitOptions.config, git runs a command the guard was supposed to block. For example, -c include.path=<file> loads a gitconfig that sets core.sshCommand, and the next fetch runs it.
  • Fix: npm install simple-git@latest (4.x is a new major, so read its changelog first). Then check that @simple-git/argv-parser resolved to 2.0.1 or later.

proxy-addr: a short IPv6 prefix trusts every client

  • Package: proxy-addr, which Express uses for req.ip and req.ips
  • Affected / fixed: 1.1.0 through 2.0.7, fixed in 2.0.8 (GHSA-jqcg-44mw-7w3h, critical, CVSS 9.1)
  • Impact: a trust subnet written as ::ffff:10.0.0.0/8 instead of /104 (or any IPv6 subnet whose leading bits are zero, such as ::/1) compiles without an error and matches every IPv4 address. Every client is then treated as a trusted proxy, so req.ip becomes whatever it puts in X-Forwarded-For. That defeats IP allowlists, rate limits and audit logs.
  • Fix: Express 4 depends on ~2.0.7 and Express 5 on ^2.0.7, so npm update proxy-addr picks up 2.0.8 without changing Express. Also check how you configure trust proxy.

Tinypool: inherited worker options load attacker code

  • Package: tinypool (about 60M weekly downloads, mostly through Vitest)
  • Affected / fixed: execArgv/env gadget up to 2.1.0, fixed in 2.1.1 (GHSA-5gmw-xhrv-c9v3, critical). run() filename gadget before 2.1.2 (GHSA-85c8-ppgw-ccpr, critical).
  • Impact: if anything in the process pollutes Object.prototype, Tinypool reads execArgv, env or filename from the prototype, and its workers load the attacker's module. It is the same issue as the Piscina bug from earlier this week.
  • Fix: npm install tinypool@latest. Vitest 3 pins tinypool@^1.1.1, a line with no fix. Vitest 4 and later no longer depend on Tinypool, so upgrading Vitest is the real fix there. The risk is limited to processes that run untrusted input next to the pool.

Seroval: thenable assimilation and unbounded TypedArrays

  • Package: seroval, the serializer behind SolidStart and TanStack Start server functions
  • Affected / fixed: fromJSON() invoking callables produced by plugins through Promise thenables, 0.12.0 through 1.6.0, fixed in 1.6.2 (GHSA-p6vx-979v-rg4c, critical, CVSS 9.8). The advisory says this bypasses the earlier 1.5.3 fix. Memory exhaustion from an unchecked TypedArray length, up to 1.6.2, fixed in 1.6.3 (GHSA-jp82-f5mq-hwhp, CVSS 7.5).
  • Impact: a tiny JSON payload can request an allocation of any size and block the event loop. On plugin-capable setups, a payload can also reach a callable it should not reach.
  • Fix: npm update seroval, then confirm 1.6.3 or later with npm ls seroval.

vm2: the 3.11.7 upgrade is not enough

Argument injection, explained

Most developers know command injection: a string reaches a shell and ; rm -rf runs. Argument injection is quieter. There is no shell. You call spawn("git", args) with an array, which is the "safe" form. But one element of that array is a value the user controls, and the program treats it as an option. Git, curl, tar, ssh and ffmpeg all accept options that run commands or read files: --upload-pack, -c core.sshCommand=…, --output, -o ProxyCommand=….

That is why simple-git ships a denylist. This week showed how fragile a denylist is. Git accepts --receive-p as a short form of --receive-pack. Config can be loaded indirectly through include.path. VISUAL works where EDITOR was blocked. Each fix closed one spelling, and the next advisory used another.

The bug shows up often in AI-generated code because agents write git and CLI wrappers all the time ("clone this repo", "check out this branch", "run git log for this path"), and they forward whatever the tool call or the request supplied:

// Vulnerable: branch comes from the request
await git.clone(repoUrl, dir, ["--branch", req.body.branch]);
// branch = "--upload-pack=touch /tmp/pwned" becomes an option, not a branch name

The fix is to make the value impossible to read as an option, and to validate its shape:

const branch = String(req.body.branch);
if (!/^[\w./-]+$/.test(branch) || branch.startsWith("-")) {
  throw new Error("invalid branch");
}
// "--" ends option parsing: everything after it is positional
await git.raw(["clone", "--branch", branch, "--", repoUrl, dir]);

The review check: for every spawn, execFile or git/CLI wrapper call, find each array element that came from outside the code. Ask two questions. Can it start with -? Is there a -- before it? If you allow config overrides at all, keep an allowlist of keys and never accept a raw -c value. Denylists of "dangerous flags" lose to abbreviations and aliases, as simple-git just showed.

Check your own repo

npm ls simple-git proxy-addr tinypool seroval vm2   # which of these are in your tree, and at what version
npm audit                                           # advisory database view of the same tree
npx guardvibe audit .                               # code + config + dependency CVEs (OSV) in one deterministic report
grep -rn "trust proxy" --include=*.{js,ts} .        # find hand-written trust subnets

Sources

Get the next one in your feed reader

Follow GuardVibe in your feed reader. No account, no email.