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=false unless strictly required.
  • Generally: key untrusted-input maps with Object.create(null) or Map, 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.

References