Introduction
A prototype pollution flaw in js-cookie, one of the most downloaded cookie handling libraries in the npm ecosystem, allows attackers to inject arbitrary attributes into cookies that developers believed were securely configured. With over 2 billion npm downloads and 9,529 direct dependents, the blast radius of CVE-2026-46625 extends across a significant portion of the client side JavaScript landscape.
What makes this vulnerability particularly interesting is its mechanism: rather than the classic global prototype pollution that mutates Object.prototype and affects every object in the runtime, this is a per-instance prototype hijack that leaves Object.prototype untouched. The polluted object is the specific merged attributes object created during each Cookies.set() call, making it harder to detect through standard pollution checks while remaining reliably exploitable.
Technical Information
Root Cause
The vulnerability lives in the assign() helper function in src/assign.mjs. This function is responsible for merging cookie attribute objects, and it uses a for...in loop with plain property assignment to copy properties from source objects onto a target:
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 for...in loop was retained for compatibility with Internet Explorer 10 and 11, which do not support Object.assign() or Object.getOwnPropertyNames() in the required capacity. This compatibility constraint directly contributed to the vulnerability's existence, as modern property copying approaches using Object.keys() or Object.assign() would not enumerate __proto__ and would not trigger the setter.
When the source object is produced by JSON.parse, 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. This replaces the target's prototype with the attacker supplied value.
The result: Object.prototype itself remains unmodified, but the merged attributes object 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(). Theassign()function merges this object usingfor...in, triggering the__proto__setter and polluting the merged attributes object's prototype. -
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 involves fetching 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 (via 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"}}
This causes js-cookie to emit a Set-Cookie string with attacker controlled domain, secure, samesite, expires, and path values, effectively bypassing the developer's intended cookie security constraints.
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 prior to 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 network |
| Attack Complexity | Low (AC:L) | No special conditions required |
| Privileges Required | None (PR:N) | No authentication needed |
| User Interaction | None (UI:N) | Automatic upon vulnerable code path execution |
| 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 the severity of cookie attribute manipulation: an attacker who can set domain to a controlled host, 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 demonstrates that the attacker controlled attributes (domain=evil.com, secure=false, samesite=None, expires=-1) are injected into the Set-Cookie string, even though the developer never specified those attributes.
This PoC simulates a Node.js environment by shimming document.cookie, but the vulnerability is equally exploitable in browser contexts where js-cookie is typically deployed.
Patch Information
The vulnerability was fixed by maintainer Klaus Hartl (carhartl) 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] } }
By short circuiting the loop iteration whenever key equals '__proto__', the dangerous setter is never invoked, and the target object's prototype chain remains intact. This is a minimal, targeted fix that preserves backward compatibility, including compatibility with IE 10/11, which the commit message explicitly mentions as a reason the codebase still uses for...in rather than modern alternatives like Object.keys() or Object.assign().
It is worth noting that the GitHub Security Advisory originally suggested a more defensive patch that also filtered 'constructor' and 'prototype' keys, and additionally replaced plain assignment with Object.defineProperty() to avoid triggering setters entirely. However, the maintainer opted for the narrower fix targeting only '__proto__', since that is the only key whose plain assignment behavior triggers the prototype setter in practice.
A corresponding 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' ) } )
This test simulates the attack scenario by creating an object via JSON.parse with a __proto__ payload, passing it to Cookies.set(), and verifying the resulting cookie string contains only the default path=/ attribute and none of the attacker injected keys.
The release notes for v3.0.7 explicitly reference CVE-2026-46625, making it straightforward for downstream consumers to 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 carries 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 represents the first publicly disclosed security vulnerability for the js-cookie v3.x line. The predecessor library, jquery-cookie, was retired and superseded by js-cookie, with no known CVE history of its own publicly tracked.
The project maintains a SECURITY.md file in its repository providing guidance on reporting vulnerabilities, indicating a responsible disclosure process is in place. The advisory was published and the patch shipped on the same day (May 16, 2026), demonstrating a rapid response timeline for an open source project maintained by a small team.
For broader context, prototype pollution vulnerabilities continue to affect major JavaScript libraries. Similar flaws have been disclosed in tough-cookie (CVE-2023-26136) and lodash (CVE-2026-2950), both involving __proto__ manipulation through unsafe merge or assign operations and sharing the same CWE-1321 classification. Adobe also released emergency patches in April 2026 following 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



