fix(cookies): keep a cookie named __proto__ in getCookies - #5663
fix(cookies): keep a cookie named __proto__ in getCookies#5663luantaraschi wants to merge 1 commit into
Conversation
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.
mcollina
left a comment
There was a problem hiding this comment.
LGTM
(However, I think this is a bad idea. We should throw hard when proto appears).
|
Thanks for the review. The three red jobs are all the same thing and it is not this change. That is a hang, not an assertion. Locally on this branch the file passes in 2 seconds: The diff only touches
|
|
Thanks for the review. The three red jobs are not from this change. They fail in On throwing hard: the repo already goes the other way in two places, and both are yours.
Both take a key that came off the wire and write it with There is one asymmetry worth naming. That said, it is your call. If you do want to throw, I would rather it be one decision applied to all three sites than cookies diverging from the two already fixed, and it is a behavior change for anyone parsing untrusted cookies today. I am happy to write that version instead if you prefer it. |
This relates to...
No open issue. Found while reading
lib/web/cookies/.Rationale
getCookies()builds its result on a plain object literal:__proto__is a valid cookie name (it is a token per RFC 6265), but assigning it on a plain object reaches theObject.prototypesetter 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:The second line is the part that bothered me more than the missing key.
getCookiesis typedRecord<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.prototypeis untouched. The bug is silent data loss plus a broken type contract.Changes
getCookiesnow builds its record with{ __proto__: null }, which is already the pattern used inlib/core/util.js,lib/util/runtime-features.js,lib/web/eventsource/eventsource.jsandlib/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 undertest/cookie/pass.Features
N/A
Bug Fixes
A cookie named
__proto__is now returned bygetCookies()like any other name.Breaking Changes and Deprecations
The returned object no longer inherits from
Object.prototype, so callingcookies.hasOwnProperty(name)on it stops working.Object.hasOwn(cookies, name)andname in cookiesstill work, as does every form of indexing and iteration.Status