NewApp Monitoring is liveSign up free
AgentbeamBeta
Open-source package vulnerability · npm

CVE-2026-104848: Tinypool: Prototype Pollution gadget in worker options leads to Remote Code Execution

criticalCVSS 4.0 9.5GHSA-5gmw-xhrv-c9v3

Tinypool, the worker pool behind Vitest, turns a prototype pollution bug elsewhere in your dependency tree into code execution. It reads worker options such as `execArgv` and `env` through the prototype chain and passes them to `new Worker()`. Versions up to 2.1.0 are affected. Upgrade to 2.1.1.

Published Oct 5, 2026 · Analysis Oct 25, 2026

What an attacker can do

Node core ignores Worker options inherited from Object.prototype. Tinypool defeats that protection. It builds this.options from an object spread, then passes this.options.env, argv, execArgv and resourceLimits explicitly. Any option the application did not set resolves through the prototype chain and becomes an own property of the Worker options.

Two keys give code execution. A polluted Object.prototype.execArgv = ['--require', '/path/to/attacker.js'] makes every pool worker load the attacker's script. A polluted Object.prototype.env = { NODE_OPTIONS: '--require ...' } does the same through the environment.

The attacker first needs a prototype pollution primitive, for example in lodash, qs, minimist or deepmerge somewhere in the process. They also need an attacker-controlled file on disk to point to. With both, they run code in every worker with the host process's privileges. Tinypool runs Vitest, so the typical targets are developer machines and CI runners holding secrets, signing keys and publishing tokens. The advisory's proof of concept confirmed both vectors on Node 20.

Who is affected

Tinypool 2.1.0 and earlier. Vitest and other tools pull it in as a dependency, so check the whole tree.

npm ls tinypool

The flaw is reachable only if the same process also has a prototype pollution bug. Every pool created while Object.prototype is polluted is affected.

tinypool

Ecosystem
npm
Affected
<= 2.1.0
Fixed in
2.1.1
Check
npm ls tinypool

How to fix it

Upgrade to tinypool 2.1.1.

npm install [email protected]

The fix is in pull request 134, titled "guard worker options from proto pollution". It makes tinypool stop reading inherited properties. The reporter's suggested fix builds the options object with Object.create(null) so lookups cannot reach the prototype. If Vitest or another tool pins an older copy, use an npm overrides entry or the pnpm or yarn equivalent to force the patched version. The advisory lists no other workaround. Fixing the underlying prototype pollution also closes the attack path.

How to tell if you were hit

Check CI and developer machines for worker processes started with an unexpected --require in their arguments, or a NODE_OPTIONS value you did not set. Look for files the build did not create, such as the proof of concept's /tmp/RCE_PROOF.txt. Audit the lockfile for prototype pollution advisories in packages like lodash, qs and minimist, since they are the likely entry point. Setting Object.prototype properties named execArgv or env at runtime is a strong sign of an attempt.

Original advisory

From the GitHub Advisory Database (CC BY 4.0).

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:

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:

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:

export default function double(n) { return n * 2 }

payload.js — attacker-controlled code (never referenced by the app):

const fs = require('fs')
fs.writeFileSync('/tmp/RCE_PROOF.txt', 'code execution achieved, pid=' + process.pid)
console.log('*** RCE ***')

app.js — normal tinypool usage:

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:

this.options = Object.assign(Object.create(null),
  kDefaultOptions, options, { filename, maxQueue: 0 });

Credited to ambushneupane.

Questions

Can an attacker exploit tinypool without another vulnerability?

No. The advisory describes tinypool as a gadget. The attacker must first pollute Object.prototype through another bug in the process. Tinypool then turns that into code execution.

Does this affect Vitest users?

Yes, if the installed tinypool is 2.1.0 or older. The advisory names Vitest as the main consumer and points to developer machines and CI runners as the exposed places. Upgrade tinypool to 2.1.1.

Is CVE-2026-104848 exploited in the wild?

No source says so, and it is not in CISA's KEV catalog. The advisory includes a working proof of concept, so the technique is public.

References

How this page is made: the data panel comes from the GitHub Advisory Database, CISA’s KEV catalog, the NVD and FIRST EPSS, and updates twice a day. The analysis is written by an AI model from the sources listed above and checked by automated rules before it is published. Check versions against the vendor's advisory before you act. Methodology

More npm vulnerabilities

All npm entries