ADVANCED TECHNIQUE BRIEF
Prototype pollution through module-loader fallbacks
Trace inherited defaults into a runtime-specific module loading gadget and distinguish loading from execution.
Also known as: 2linenodejs · CommonJS prototype gadget
A pollution primitive is useful only when a later read gives the polluted property authority. Runtime internals and error handling can provide that read even when the application’s main success path appears too small to exploit.
Research anchor and scope
Zeyu’s 2linenodejs analysis follows polluted properties into a historical CommonJS trySelf fallback and then a Yarn v1.22.19 preinstall script. It first establishes control over module resolution, then needs a separate script gadget to start a subprocess. The page does not establish a universal affected Node version range. Match the supplied runtime’s loader source and installed files before reusing the chain.
Track reads, not just writes
In JavaScript, ordinary property lookup searches an object’s prototype chain. An object with no own mode property can therefore acquire a value through Object.prototype. Distinguish a property that was parsed as data from one that changes a prototype: JSON parsing alone does not automatically pollute every object. The dangerous step is typically a merge or assignment that treats a special key as a normal destination path.
A new toy example is const {path} = lookupResult || {};. When the lookup fails, destructuring reads path from an ordinary empty object. If code expects this to be undefined but inherited properties are permitted, the fallback can unexpectedly become attacker controlled. Compare with an explicit own-property check or a null-prototype dictionary to understand the missing invariant.
Build a gadget graph
Label each edge with what becomes controlled: a module identifier, a package root, a package export, an argument, or an environment-like property. Do not collapse these into “RCE.” Loading an arbitrary JavaScript file is limited by available files and loader checks. Executing a fixed program is limited by its arguments. A shell string and an execFile argument vector are different capabilities.
Inspect failure branches after pollution. An exception might cause a second module load, while a successful request exits before any useful property is read. Establish ordering explicitly: pollution must survive until the gadget runs, and the exception must occur after the relevant write. Restart boundaries and worker isolation can eliminate persistence assumptions.
Version and environment limits
Module-loader internals change between releases. Package scope caches may prevent a lookup, and an existing own property can mask an inherited value. Installed helper scripts, package-manager versions, argument parsing, and subprocess options are all part of the chain’s prerequisites. Test the smallest non-destructive observable effect for each edge, such as selecting a benign module or altering a diagnostic option. Pollution without a reachable gadget remains an integrity problem, but its concrete execution impact must be demonstrated separately.