Skip to content

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

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

fix(cookies): keep a cookie named __proto__ in getCookies#5663
luantaraschi wants to merge 1 commit 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.

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.

3 participants