Skip to content

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

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

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

Conversation

@renovate

@renovate renovate Bot commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

This PR contains the following updates:

Package Change Age Confidence
js-cookie 2.2.1 → 3.0.7 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)

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.

@renovate renovate Bot added dependencies Pull requests that update a dependency file security labels Aug 26, 2026
@renovate

renovate Bot commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor Author

⚠️ Artifact update problem

Renovate failed to update an artifact related to this branch. You probably do not want to merge this PR as-is.

♻ Renovate will retry this branch, including artifacts, only when one of the following happens:

  • any of the package files in this branch needs updating, or
  • the branch becomes conflicted, or
  • you click the rebase/retry checkbox if found above, or
  • you rename this PR's title to start with "rebase!" to trigger it manually

The artifact failure details are included below:

File name: zapisy/yarn.lock
➤ YN0000: ┌ Resolution step
➤ YN0000: └ Completed in 0.25s
➤ YN0000: ┌ Fetch step
➤ YN0001: │ TypeError: fsevents@patch:fsevents@npm%3A1.2.13#builtin<compat/fsevents>::version=1.2.13&hash=87eb42: (0 , A.isDate) is not a function
    at /tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:419607
    at C.utimesImpl (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:419666)
    at C.utimesSync (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:419065)
    at C.utimesPromise (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:418931)
    at C.mkdirpPromise (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:383563)
    at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
    at async z.fetchers.patchPackage (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:217976)
    at async Q (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:244809)
    at async v (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:245268)
    at async /tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:245959
➤ YN0001: │ TypeError: fsevents@patch:fsevents@npm%3A2.1.2#builtin<compat/fsevents>::version=2.1.2&hash=87eb42: (0 , A.isDate) is not a function
    at /tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:419607
    at C.utimesImpl (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:419666)
    at C.utimesSync (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:419065)
    at C.utimesPromise (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:418931)
    at C.mkdirpPromise (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:383563)
    at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
    at async z.fetchers.patchPackage (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:217976)
    at async Q (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:244809)
    at async v (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:245268)
    at async /tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:245959
➤ YN0001: │ TypeError: resolve@patch:resolve@npm%3A1.20.0#builtin<compat/resolve>::version=1.20.0&hash=3388aa: (0 , A.isDate) is not a function
    at /tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:419607
    at C.utimesImpl (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:419666)
    at C.utimesSync (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:419065)
    at C.utimesPromise (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:418931)
    at C.mkdirpPromise (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:383563)
    at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
    at async z.fetchers.patchPackage (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:217976)
    at async Q (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:244809)
    at async v (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:245268)
    at async /tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:245959
➤ YN0001: │ TypeError: typescript@patch:typescript@npm%3A4.0.2#builtin<compat/typescript>::version=4.0.2&hash=5b02a2: (0 , A.isDate) is not a function
    at /tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:419607
    at C.utimesImpl (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:419666)
    at C.utimesSync (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:419065)
    at C.utimesPromise (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:418931)
    at C.mkdirpPromise (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:383563)
    at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
    at async z.fetchers.patchPackage (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:217976)
    at async Q (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:244809)
    at async v (/tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:245268)
    at async /tmp/renovate/repos/github/iiuni/projektzapisy/zapisy/.yarn/releases/yarn-berry.js:2:245959
➤ YN0013: │ 950 packages were already cached, 5 had to be fetched
➤ YN0000: └ Completed in 0.88s
➤ YN0000: Failed with errors in 1.13s

@renovate
renovate Bot force-pushed the renovate/npm-js-cookie-vulnerability branch 2 times, most recently from 6e0abfe to f737d22 Compare September 24, 2026 09:06
@renovate
renovate Bot force-pushed the renovate/npm-js-cookie-vulnerability branch from f737d22 to c6683d4 Compare October 5, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file security

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants