Skip to content

Fix CookieMap.toJSON() crash with numeric cookie names#28314

Merged
alii merged 4 commits into
mainfrom
farm/f292eb8d/fix-cookiemap-tojson-index
Mar 20, 2026
Merged

Fix CookieMap.toJSON() crash with numeric cookie names#28314
alii merged 4 commits into
mainfrom
farm/f292eb8d/fix-cookiemap-tojson-index

Conversation

@robobun

@robobun robobun commented Mar 20, 2026

Copy link
Copy Markdown
Collaborator

CookieMap.toJSON() used putDirect to set properties on the result object, which asserts that the property name is not an array index. Cookie names can be numeric strings (e.g. "0", "1", "42"), which parse as array indices and trigger the assertion in JSObject::putDirectInternal.

Use putDirectMayBeIndex instead, which correctly handles both indexed and named properties. This is consistent with how other Bun code handles user-controlled property names (e.g. FormData, FetchHeaders, environment variables).

Crash fingerprint: JSObjectInlines.h(451)ASSERTION FAILED: !parseIndex(propertyName)

Minimal repro:

const map = new Bun.CookieMap("0=first; 1=second; 42=answer");
map.toJSON();

Verification: CI building (Buildkite #40296, lint/format passing). Diff is 2-line fix replacing putDirectputDirectMayBeIndex in both loops of CookieMap::toJSON, matching the established pattern used in ZigGlobalObject.cpp (env vars), JSDOMFormData.cpp, JSFetchHeaders.cpp, and NodeHTTP.cpp. Regression test in cookie-map.test.ts exercises numeric cookie names ("0", "1", "42") through toJSON(). No TODO/FIXME/HACK markers. No unrelated changes.

CookieMap.toJSON() used putDirect to set properties on the result
object, which asserts that the property name is not an array index.
Cookie names can be numeric strings (e.g. '0', '1', '42'), which
parse as array indices and trigger the assertion.

Use putDirectMayBeIndex instead, which correctly handles both indexed
and named properties.
@robobun

robobun commented Mar 20, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 9:20 AM PT - Mar 20th, 2026

@robobun, your commit 944a26658cfa790358fa90e08309d56c14f17eeb passed in Build #40319! 🎉


🧪   To try this PR locally:

bunx bun-pr 28314

That installs a local version of the PR into your bun-28314 executable, so you can run:

bun-28314 --bun

@coderabbitai

coderabbitai Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: afe0a648-bf68-467b-acb2-f377547830cf

📥 Commits

Reviewing files that changed from the base of the PR and between a4b13a0 and f46af31.

📒 Files selected for processing (1)
  • src/bun.js/bindings/CookieMap.cpp

Walkthrough

Reworked CookieMap::toJSON to track cookie names with a HashSet<String> seenKeys and insert properties via putDirectMayBeIndex(...), preventing duplicates by checking seenKeys instead of querying object properties. Added tests for numeric-looking and Object.prototype-named cookie keys.

Changes

Cohort / File(s) Summary
CookieMap Serialization Update
src/bun.js/bindings/CookieMap.cpp
Introduced HashSet<String> seenKeys to record cookie names from modified cookies; use putDirectMayBeIndex(globalObject, ...) for property insertion; skip duplicates by testing seenKeys.add(...).isNewEntry when adding original cookies instead of checking existing object properties.
CookieMap Serialization Tests
test/js/bun/cookie/cookie-map.test.ts
Added tests asserting Bun.CookieMap.toJSON() preserves cookie names as object keys for numeric-looking strings (e.g., 0, 1, 42) and names colliding with Object.prototype (e.g., toString, constructor, valueOf).
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing a crash in CookieMap.toJSON() when cookie names are numeric strings.
Description check ✅ Passed The description covers both required sections: what the PR does (explains the bug and solution) and how it was verified (CI, testing, pattern matching).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

Found 3 issues this PR may fix:

  1. Defining Object.prototype["1"] setter causes Bun to panic during env access #20514 - Defining Object.prototype["1"] setter causes Bun to panic during env access - similar numeric property handling assertion failure
  2. Bun.CookieMap & Bun.Cookie are 1.5x+ slower than pure js libraries #19216 - Bun.CookieMap & Bun.Cookie are 1.5x+ slower than pure js libraries - CookieMap performance issue that could benefit from the putDirectMayBeIndex() optimization
  3. Standardize Cookie serialization methods between Cookie and CookieMap #18795 - Standardize Cookie serialization methods between Cookie and CookieMap - CookieMap API consistency issue related to serialization methods like toJSON()

If this is helpful, consider adding Fixes #<number> to the PR description to auto-close the issue on merge.

🤖 Generated with Claude Code

@claude claude Bot 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.

LGTM — straightforward crash fix replacing putDirect with putDirectMayBeIndex, consistent with how FormData, FetchHeaders, and other bindings handle user-controlled property names.

Extended reasoning...

Overview

This PR fixes a crash in CookieMap::toJSON() when cookie names are numeric strings (e.g., "0", "1", "42"). The fix changes two calls from putDirect to putDirectMayBeIndex in src/bun.js/bindings/CookieMap.cpp, and adds a corresponding test case.

Security risks

None. The change moves from a method that asserts non-index property names to one that correctly handles both indexed and named properties. No new attack surface is introduced.

Level of scrutiny

This is a minimal, low-risk bug fix. The diff is 2 changed lines in C++ plus a 9-line test. The putDirectMayBeIndex pattern is already established across 6 other files in the bindings directory (JSFetchHeaders.cpp, JSDOMFormData.cpp, bindings.cpp, ZigGlobalObject.cpp, NodeHTTP.cpp, SerializedScriptValue.cpp), confirming this is the standard approach for user-controlled property names in Bun.

Other factors

  • No CODEOWNERS rules cover the modified files.
  • No outstanding reviewer comments.
  • The pre-existing bug report about hasProperty traversing the prototype chain is valid but is not introduced by this PR — it exists in the unchanged hasProperty call on line 245 and predates this change.
  • The test directly exercises the crash scenario from the PR description.

Comment thread src/bun.js/bindings/CookieMap.cpp Outdated
…hain

Use getDirectOffset instead of hasProperty when deduplicating original
cookies against modified cookies. hasProperty traverses the prototype
chain, causing cookies named 'toString', 'constructor', etc. to be
silently dropped from the output.

@claude claude Bot 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.

LGTM — small, focused crash fix that follows established patterns (putDirectMayBeIndex) and also fixes the prototype chain deduplication issue.

Extended reasoning...

Overview

This PR touches two files: CookieMap.cpp (the fix) and cookie-map.test.ts (regression tests). The code change is minimal — replacing putDirect with putDirectMayBeIndex in both loops of toJSON(), and replacing hasProperty with getDirectOffset for own-property-only deduplication. Both changes follow established patterns used elsewhere in the codebase (FormData, FetchHeaders, env vars).

Security risks

None. Cookie names are user-controlled strings, but putDirectMayBeIndex is the correct JSC API for handling arbitrary property names. The switch from hasProperty to getDirectOffset actually improves correctness by avoiding prototype chain traversal.

Level of scrutiny

This is a straightforward 2-line bug fix with clear intent (preventing an assertion failure crash) and good test coverage. The pattern is well-established in the codebase. Low scrutiny is appropriate.

Other factors

The nit-level bug report about getDirectOffset not finding indexed properties for numeric key deduplication is technically valid — if the same numeric cookie name appears twice in m_originalCookies, the second value wins instead of the first. However, this is an extremely rare edge case (duplicate numeric cookie names in a single cookie string), and the PR is still a net improvement since numeric cookie names previously caused a crash. The PR includes two well-targeted regression tests covering both numeric names and Object.prototype property names.

Comment thread src/bun.js/bindings/CookieMap.cpp Outdated
getDirectOffset only searches named property storage, missing numeric
keys stored in indexed storage by putDirectMayBeIndex. Use a HashSet
for deduplication, consistent with FormData and URLSearchParams.

@claude claude Bot 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.

LGTM — all previous feedback addressed; the HashSet dedup + putDirectMayBeIndex fix is clean and follows established patterns.

Extended reasoning...

Overview

This PR fixes a crash in CookieMap::toJSON() when cookie names are numeric strings (e.g., "0", "1", "42") by replacing putDirect with putDirectMayBeIndex. It also replaces the hasProperty-based deduplication (which traversed the prototype chain) with a HashSet<String>, fixing silent data loss for cookies named "toString", "constructor", etc. Two files changed: CookieMap.cpp (the fix) and cookie-map.test.ts (regression tests).

Security risks

None. This is a bugfix in cookie serialization that makes the code more robust against unusual but valid cookie names. No auth, crypto, or permission-related code is affected.

Level of scrutiny

Low scrutiny warranted. The change is small (~10 lines of logic), self-contained in a single method, and follows the exact same pattern used by FormData (JSDOMFormData.cpp) and URLSearchParams (JSURLSearchParams.cpp). The regression tests exercise the specific edge cases being fixed.

Other factors

I previously left two detailed comments identifying the hasProperty prototype chain bug and the getDirectOffset indexed storage mismatch. Both have been fully addressed in the final revision — the HashSet<String> approach was exactly what I recommended. The first inline comment is marked as resolved. No CODEOWNERS match for these files. CodeRabbit also found no actionable issues.

@claude claude Bot 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.

LGTM — clean fix addressing the crash and both previously flagged deduplication issues.

Extended reasoning...

Overview

This PR fixes a crash in CookieMap::toJSON() triggered by numeric cookie names (e.g., "0", "1", "42") that parse as array indices and hit a JSC assertion in putDirect. The fix replaces putDirect with putDirectMayBeIndex in both loops and replaces the hasProperty-based deduplication with a HashSet<String> seenKeys, matching the pattern used by FormData, URLSearchParams, and FetchHeaders elsewhere in Bun. Two regression tests are added covering numeric names and Object.prototype-colliding names.

Security risks

None. The change only affects how cookie key-value pairs are written to a plain JS object during serialization. No auth, crypto, or permission logic is involved.

Level of scrutiny

This is a small, targeted bug fix (2 files, ~20 lines changed) in a non-security-critical serialization path. The fix follows well-established patterns already used throughout the codebase. Low scrutiny is appropriate.

Other factors

I previously raised two issues on this PR: (1) hasProperty traversing the prototype chain, silently dropping cookies named "toString", "constructor", etc., and (2) getDirectOffset failing to detect indexed properties, breaking dedup for numeric cookie names. Both have been addressed by the HashSet<String> approach, and both inline comments are resolved. The test coverage exercises both edge cases. CodeRabbit also found no actionable issues.

@alii alii merged commit 9933f7a into main Mar 20, 2026
64 checks passed
@alii alii deleted the farm/f292eb8d/fix-cookiemap-tojson-index branch March 20, 2026 20:52
structwafel pushed a commit to structwafel/bun that referenced this pull request Apr 25, 2026
CookieMap.toJSON() used `putDirect` to set properties on the result
object, which asserts that the property name is not an array index.
Cookie names can be numeric strings (e.g. `"0"`, `"1"`, `"42"`), which
parse as array indices and trigger the assertion in
`JSObject::putDirectInternal`.

Use `putDirectMayBeIndex` instead, which correctly handles both indexed
and named properties. This is consistent with how other Bun code handles
user-controlled property names (e.g. FormData, FetchHeaders, environment
variables).

**Crash fingerprint:** `JSObjectInlines.h(451)` — `ASSERTION FAILED:
!parseIndex(propertyName)`

**Minimal repro:**
```js
const map = new Bun.CookieMap("0=first; 1=second; 42=answer");
map.toJSON();
```

---
**Verification:** CI building (Buildkite #40296, lint/format passing).
Diff is 2-line fix replacing `putDirect` → `putDirectMayBeIndex` in both
loops of `CookieMap::toJSON`, matching the established pattern used in
ZigGlobalObject.cpp (env vars), JSDOMFormData.cpp, JSFetchHeaders.cpp,
and NodeHTTP.cpp. Regression test in cookie-map.test.ts exercises
numeric cookie names ("0", "1", "42") through toJSON(). No
TODO/FIXME/HACK markers. No unrelated changes.

---

Co-authored-by: Alistair Smith <hi@alistair.sh>
xhjkl pushed a commit to xhjkl/bun that referenced this pull request May 14, 2026
CookieMap.toJSON() used `putDirect` to set properties on the result
object, which asserts that the property name is not an array index.
Cookie names can be numeric strings (e.g. `"0"`, `"1"`, `"42"`), which
parse as array indices and trigger the assertion in
`JSObject::putDirectInternal`.

Use `putDirectMayBeIndex` instead, which correctly handles both indexed
and named properties. This is consistent with how other Bun code handles
user-controlled property names (e.g. FormData, FetchHeaders, environment
variables).

**Crash fingerprint:** `JSObjectInlines.h(451)` — `ASSERTION FAILED:
!parseIndex(propertyName)`

**Minimal repro:**
```js
const map = new Bun.CookieMap("0=first; 1=second; 42=answer");
map.toJSON();
```

---
**Verification:** CI building (Buildkite #40296, lint/format passing).
Diff is 2-line fix replacing `putDirect` → `putDirectMayBeIndex` in both
loops of `CookieMap::toJSON`, matching the established pattern used in
ZigGlobalObject.cpp (env vars), JSDOMFormData.cpp, JSFetchHeaders.cpp,
and NodeHTTP.cpp. Regression test in cookie-map.test.ts exercises
numeric cookie names ("0", "1", "42") through toJSON(). No
TODO/FIXME/HACK markers. No unrelated changes.

---

Co-authored-by: Alistair Smith <hi@alistair.sh>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants