Prototype Pollution in tinypool Lets Attackers Hijack run() to Execute Arbitrary Worker Code
What Happened
A GitHub Security Advisory (GHSA-85c8-ppgw-ccpr, tracked as CVE-2026-104849) discloses a prototype-pollution-to-RCE gadget in tinypool, the worker pool library forked from piscina. According to the advisory, when pool.run(task, options) is called, tinypool reads the filename property from the supplied options object. If that object lacks its own filename property, the lookup falls through the prototype chain to Object.prototype. An attacker who has already polluted Object.prototype.filename elsewhere in the application (the advisory cites lodash.merge or qs.parse as common vectors) can redirect tinypool to load and execute an attacker-supplied worker module instead of the intended one. A working proof-of-concept is public. This is described as the tinypool counterpart to an earlier root-cause finding in piscina (GHSA-x9g3-xrwr-cwfg). The advisory notes pool.run(task) called without an options argument is not affected.
Why It Matters
Any Node.js application using tinypool that (a) is independently vulnerable to prototype pollution and (b) calls pool.run() with an attacker-influenced or merely present options object is exposed to full arbitrary JavaScript execution inside the worker pool. Per the advisory's proof-of-concept, this chain can let an attacker swap in a malicious worker that intercepts and exfiltrates request data passed as the task — turning a prototype-pollution bug elsewhere in the stack into code execution and data theft via tinypool. Severity hinges entirely on whether a prototype-pollution primitive exists somewhere upstream; tinypool's own code does not pollute the prototype, but it trusts the prototype chain when resolving filename.
What Defenders Should Do Now
- Inventory whether your Node.js services depend on
tinypool(directly, or transitively via tooling like Vitest) and check whether a patched release addressing this advisory is available; upgrade when it is. - Audit for prototype-pollution-prone dependencies in the same process (older
lodash/lodash.merge,qs, and similar deep-merge/parse utilities) — eliminating the pollution primitive breaks this chain regardless of tinypool's patch status. - Review application code for calls to
pool.run(task, options)where the options object is built from or merged with user-controlled input; avoid passing untrusted objects directly asrun()options. - As a mitigation ahead of patching, consider hardening object construction with
Object.freeze(Object.prototype)or defensiveObject.hasOwnchecks in code that merges untrusted input into objects used downstream. - From a hunting perspective, monitor for worker processes loading unexpected module paths, or for Node.js processes spawning child/worker threads pointing outside the application's expected working directory.
Developing Intel
This is a same-day GHSA disclosure with a public proof-of-concept; patch availability and downstream impact across projects depending on tinypool (such as test runners) are still being assessed. For full technical details and remediation guidance, see the original advisory: GHSA-85c8-ppgw-ccpr.