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>.cmdbypass in 3.15.0 through 4.0.0, fixed in 4.0.1 (GHSA-x6jw-m9v5-85vh, critical).include.pathconfig loading (GHSA-g4wm-2vf7-vfgr, CVSS 8.1) and abbreviated--receive-poptions (GHSA-858h-whjf-mvg5, CVSS 8.1) affect versions up to 3.36.0 and are fixed in 4.0.0. The parser ignored theVISUALeditor variable in@simple-git/argv-parserbefore 2.0.1 (GHSA-v5rq-49vh-5v5c, critical). - Impact: if attacker-influenced text reaches
customArgs,-cconfig orSimpleGitOptions.config, git runs a command the guard was supposed to block. For example,-c include.path=<file>loads a gitconfig that setscore.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-parserresolved to 2.0.1 or later.
proxy-addr: a short IPv6 prefix trusts every client
- Package:
proxy-addr, which Express uses forreq.ipandreq.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/8instead 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, soreq.ipbecomes whatever it puts inX-Forwarded-For. That defeats IP allowlists, rate limits and audit logs. - Fix: Express 4 depends on
~2.0.7and Express 5 on^2.0.7, sonpm update proxy-addrpicks up 2.0.8 without changing Express. Also check how you configuretrust proxy.
Tinypool: inherited worker options load attacker code
- Package:
tinypool(about 60M weekly downloads, mostly through Vitest) - Affected / fixed:
execArgv/envgadget up to 2.1.0, fixed in 2.1.1 (GHSA-5gmw-xhrv-c9v3, critical).run()filenamegadget before 2.1.2 (GHSA-85c8-ppgw-ccpr, critical). - Impact: if anything in the process pollutes
Object.prototype, Tinypool readsexecArgv,envorfilenamefrom 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 pinstinypool@^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 withnpm ls seroval.
vm2: the 3.11.7 upgrade is not enough
- Package:
vm2 - Affected / fixed: five issues are fixed in 3.11.8: a NodeVM escape through the host
__proto__accessor (GHSA-88hf-g992-jg85, CVSS 10), an AggregateError host RCE (GHSA-x965-fc75-jpqh), mutation of TypedArray intrinsics (GHSA-3vgf-8m4q-q4qr), a bypass of the unhandled-rejection hardening (GHSA-gjq8-xm47-88rc) and anallowAsync: falsebypass (GHSA-f8gf-w286-fmq2). Two more are fixed only in 3.12.2: a path-prefix bypass in the custom resolver (GHSA-5h3f-q97h-ccvc, CVSS 10) and a host process crash from a rejected Promise (GHSA-2v2p-6j97-cjg9). Four more advisories published the same day are already fixed in 3.11.7 (GHSA-j3hm-6rg5-mchv, GHSA-fcqc-726x-5wfc, GHSA-wjwh-qqvp-g4p4, GHSA-r4fx-v8hh-22mv). - Fix:
npm install vm2@3.12.2. Better still, move untrusted code to isolated-vm or a microVM sandbox.
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
- GHSA-x6jw-m9v5-85vh — simple-git trailer command config
- GHSA-g4wm-2vf7-vfgr — simple-git include.path
- GHSA-858h-whjf-mvg5 — simple-git long-option abbreviation
- GHSA-v5rq-49vh-5v5c — @simple-git/argv-parser VISUAL
- GHSA-jqcg-44mw-7w3h — proxy-addr IPv4-mapped trust subnet
- GHSA-5gmw-xhrv-c9v3 — Tinypool worker options gadget
- GHSA-85c8-ppgw-ccpr — Tinypool run() filename gadget
- GHSA-p6vx-979v-rg4c — Seroval thenable assimilation
- GHSA-jp82-f5mq-hwhp — Seroval TypedArray memory exhaustion
- GHSA-5h3f-q97h-ccvc — vm2 custom resolution boundary
- GHSA-2v2p-6j97-cjg9 — vm2 host Promise rejection crash
- GHSA-88hf-g992-jg85 — vm2 NodeVM sandbox escape