Skip to content

Update dependency js-cookie to v3 [SECURITY] - #74

Open
renovate[bot] wants to merge 1 commit into
masterfrom
renovate/npm-js-cookie-vulnerability
Open

Update dependency js-cookie to v3 [SECURITY]#74
renovate[bot] wants to merge 1 commit into
masterfrom
renovate/npm-js-cookie-vulnerability

Conversation

@renovate

@renovate renovate Bot commented Aug 26, 2026

Copy link
Copy Markdown

This PR contains the following updates:

Package Change Age Confidence
js-cookie ^2.2.0^3.0.0 age confidence

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

CVE-2026-46625 / GHSA-qjx8-664m-686j

More information

Details

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:
cls-2026-05-14-01 44 39

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]
}

Severity

  • CVSS Score: 7.5 / 10 (High)
  • Vector String: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N

References

This data is provided by the GitHub Advisory Database (CC-BY 4.0).


Release Notes

js-cookie/js-cookie (js-cookie)

v3.0.7

Compare Source

  • Prevent cookie attribute injection: CVE-2026-46625 (eb3c40e)
  • Add Partitioned attribute to readme (b994768)
  • Publish to npm registry via trusted publisher exclusively (4dc71be)
  • Ensure consistent behaviour for get('name') + get() (1953d30)

v3.0.6

Compare Source

v3.0.5

Compare Source

  • Remove npm version restriction in package.json - #​818

v3.0.4

Compare Source

  • Publish to npmjs.com with package provenance

v3.0.3

Compare Source

v3.0.2

Compare Source

v3.0.1

Compare Source

  • Make package.json accessible in export - #​727

v3.0.0

Compare Source

  • Removed defaults in favor of a builder: now to supply an api instance with particular predefined (cookie) attributes there's Cookies.withAttributes(), e.g.:
const api = Cookies.withAttributes({
  path: '/',
  secure: true
})
api.set('key', 'value') // writes cookie with path: '/' and secure: true...
  • The attributes that an api instance is configured with are exposed as attributes property; it's an immutable object and unlike defaults cannot be changed to configure the api.
  • The mechanism to fall back to the standard, internal converter by returning a falsy value in a custom read converter has been removed. Instead the default converters are now exposed as Cookies.converter, which allows for implementing self-contained custom converters providing the same behavior:
const customReadConverter = (value, name) => {
  if (name === 'special') {
    return unescape(value)
  }
  return Cookies.converter.read(value)
}
  • withConverter() no longer accepts a function as argument to be turned into a read converter. It is now required to always pass an object with the explicit type(s) of converter(s):
const api = Cookies.withConverter({
  read: (value, name) => unescape(value)
})
  • The converter(s) that an api instance is configured with are exposed as converter property; it's an immutable object and cannot be changed to configure the api.
  • Started providing library as ES module, in addition to UMD module. The module field in package.json points to an ES module variant of the library.
  • Started using browser field instead of main in package.json (for the UMD variant of the library).
  • Dropped support for IE < 10.
  • Removed built-in JSON support, i.e. getJSON() and automatic stringifying in set(): use Cookies.set('foo', JSON.stringify({ ... })) and JSON.parse(Cookies.get('foo')) instead.
  • Removed support for Bower.
  • Added minified versions to package - #​501
  • Improved support for url encoded cookie values (support case insensitive encoding) - #​466, #​530
  • Expose default path via API - #​541
  • Handle falsy arguments passed to getters - #​399
  • No longer support Node < 12 when building (LTS versions only)

v2.2.1

Compare Source

  • #​400: Prevent XSS in the cookie attributes
  • #​350: Document npm package manager usage

Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants