Skip to content

fix(cookies): keep a cookie named __proto__ in getCookies - #5663

Open
luantaraschi wants to merge 2 commits into
nodejs:mainfrom
luantaraschi:fix/get-cookies-proto-key
Open

fix(cookies): keep a cookie named __proto__ in getCookies#5663
luantaraschi wants to merge 2 commits into
nodejs:mainfrom
luantaraschi:fix/get-cookies-proto-key

Conversation

@luantaraschi

@luantaraschi luantaraschi commented Aug 7, 2026

Copy link
Copy Markdown

This relates to...

No open issue. Found while reading lib/web/cookies/.

Rationale

getCookies() builds its result on a plain object literal:

const out = {}
for (const piece of cookie.split(';')) {
  const [name, ...value] = piece.split('=')
  out[name.trim()] = value.join('=')
}

__proto__ is a valid cookie name (it is a token per RFC 6265), but assigning it on a plain object reaches the Object.prototype setter instead of creating an own property. The setter only accepts an object or null, and the assigned value here is always a string, so it does nothing and the cookie is dropped:

const headers = new Headers()
headers.set('Cookie', '__proto__=foo; bar=baz')

getCookies(headers)              // { bar: 'baz' }, the first cookie is gone
getCookies(headers).__proto__    // Object.prototype, not the string 'foo'

The second line is the part that bothered me more than the missing key. getCookies is typed Record<string, string>, so a caller that indexes it by an attacker-influenced name gets an object back where the types promise a string.

There is no prototype pollution here: the setter refuses a string, so Object.prototype is untouched. The bug is silent data loss plus a broken type contract.

Changes

getCookies now builds its record with { __proto__: null }, which is already the pattern used in lib/core/util.js, lib/util/runtime-features.js, lib/web/eventsource/eventsource.js and lib/web/fetch/formdata.js.

The existing tests all use assert.deepEqual, which does not compare prototypes, so none of them needed changing. All 92 tests under test/cookie/ pass.

Features

N/A

Bug Fixes

A cookie named __proto__ is now returned by getCookies() like any other name.

Breaking Changes and Deprecations

The returned object no longer inherits from Object.prototype, so calling cookies.hasOwnProperty(name) on it stops working. Object.hasOwn(cookies, name) and name in cookies still work, as does every form of indexing and iteration.

Status

getCookies built its result on a plain object literal, so assigning the
__proto__ key reached the Object.prototype setter instead of creating an
own property. The assigned value is a string, so the setter is a no-op
and the cookie is dropped from the returned record.

Reading that key back also returned Object.prototype, which breaks the
documented Record<string, string> contract.

Use a null-prototype object, as lib/ already does in six other places.
Copilot AI lite review requested due to automatic review settings August 7, 2026 16:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@mcollina mcollina left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

(However, I think this is a bad idea. We should throw hard when proto appears).

@luantaraschi

luantaraschi commented Aug 8, 2026

Copy link
Copy Markdown
Author

Thanks for the review. I checked the three failing jobs, and none of them exercises this change, which only touches lib/web/cookies/.

  • The two Node 24 Ubuntu jobs fail in test/http2-request-never-settles.js. The same test failed on main in run 31085361670 on 6 August, before this branch existed.
  • The Node 22 macOS job times out in test/fetch/content-length.js at 180006 ms, matching the 180-second limit rather than an assertion failure.

On __proto__, I followed the behavior already used in two places:

Both preserve keys received from the wire with Object.defineProperty instead of rejecting them. This PR uses the same approach in getCookies.

__proto__ is also a valid cookie name under the token grammar in RFC 6265 section 4.1.1. Throwing from getCookies would therefore let an incoming cookie break its caller. If the project prefers rejecting this key, I think that policy should be applied consistently to all three parsing paths rather than only to cookies. I am happy to help with that as a separate change.

A rerun should be enough for this PR. I can rebase if you would prefer a fresh run that way.

@luantaraschi

Copy link
Copy Markdown
Author

Thanks for the review. One thing to flag, and it is on me: about a minute after you approved I merged main into the branch to get the old red run out of the way, and the new head now reports no checks at all.

I cannot start workflow runs in this repository myself, so it needs someone with write access to kick them off before this can land. Sorry for the extra step, the timing was clumsy.

@codecov-commenter

codecov-commenter commented Aug 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.46%. Comparing base (692c02b) to head (90d7914).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #5663   +/-   ##
=======================================
  Coverage   93.46%   93.46%           
=======================================
  Files         110      110           
  Lines       38777    38779    +2     
=======================================
+ Hits        36244    36246    +2     
  Misses       2533     2533           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

5 participants