An independent re-analysis by Goldberg Security Research of a publicly disclosed vulnerability: our breakdown of root cause, exploitation, and defence. Discovery credit belongs to the original researchers (see references).
Summary
tough-cookie backs the cookie jars used by many HTTP clients. Its in-memory store
indexed cookies with a plain object literal, which inherits from Object.prototype.
When run with rejectPublicSuffixes=false, a cookie whose domain/path is
__proto__ pollutes the global prototype — and in JavaScript, that contaminates
every object in the process.
Root cause
The memstore index was created as {}. Any object literal carries a live link to
Object.prototype, so writing a key like __proto__ walks up the
prototype chain instead of creating an own-property:
// vulnerable: index inherits Object.prototype
this.idx = {};
// storing a cookie keyed by "__proto__" pollutes the global prototype
// fix: a prototype-less map — keys can never reach Object.prototype
this.idx = Object.create(null);
With rejectPublicSuffixes=false, the normally-rejected domain/path values reach the
store, so an attacker who controls cookies can inject arbitrary properties onto
Object.prototype.
Impact
Polluted prototype properties are visible to the whole application. Consequences range from denial of service and logic corruption to remote code execution, depending on which gadget properties the surrounding app reads from inherited objects.
Remediation
- Upgrade to tough-cookie ≥ 4.1.3 (index now uses
Object.create(null)). - Avoid
rejectPublicSuffixes=falseunless strictly required. - Generally: key untrusted-input maps with
Object.create(null)orMap, never{}.
Takeaway
{} versus Object.create(null) for any map keyed by external input is a
recurring JavaScript footgun. If untrusted strings can become object keys, the prototype chain
is part of your attack surface.