FormData: match WebKit's multipart boundary exactly#29631
Conversation
|
Updated 2:12 AM PT - Apr 23rd, 2026
❌ @robobun, your commit ce16160 has 4 failures in
🧪 To try this PR locally: bunx bun-pr 29631That installs a local version of the PR into your bun-29631 --bun |
There was a problem hiding this comment.
LGTM — single format-string change to match WebKit's boundary, buffer still fits, well-covered by new tests.
Extended reasoning...
Overview
This PR changes one format string in src/bun.js/webcore/Blob.zig:588 from "-WebkitFormBoundary{x}" to "----WebKitFormBoundary{x}", aligning Bun's generated multipart boundary with WebKit's exact format (4 leading dashes, capital K). Two regression tests are added to test/js/bun/http/form-data-set-append.test.js asserting the declared boundary, body opener (6 dashes), and terminator.
Security risks
None. The boundary is still derived from a random UUID; only the static prefix changes. No parsing, auth, crypto, or permission logic is touched.
Level of scrutiny
Low. This is a one-line literal change in production code. I verified:
- The new prefix (22 bytes) + 32 hex chars = 54 bytes, which fits the existing
[70]u8buffer and stays within RFC 2046's 70-char boundary limit. - The downstream usage at lines 603–605/609 reads
boundaryas an opaque slice, so the extra dashes flow through correctly to body markers and theContent-Typeheader without further changes. - Other repo references to
WebKitFormBoundary(instreams.test.jsandbun.d.ts) are independent hardcoded fixtures/docs for parsing and don't depend on Bun's generated boundary format.
Other factors
No bugs found by the bug hunter, no outstanding reviewer comments, and the PR includes a gate check showing the new tests fail on the old code and pass on the new code. The change is mechanical and self-contained.
WalkthroughFormData multipart boundary generation changed from Changes
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
CI red — investigated, all failures are unrelated pre-existing Windows/memory flakes, nothing touches FormData/Blob:
None of the failing tests reference FormData, Blob, or multipart code. Can a maintainer please retry the failed jobs? |
11231fe to
b889951
Compare
There was a problem hiding this comment.
LGTM — one-line format string fix with regression tests; the inline comment-ordering nit is non-blocking.
Extended reasoning...
Overview
This PR changes a single format string in src/bun.js/webcore/Blob.zig:588 from "-WebkitFormBoundary{x}" to "----WebKitFormBoundary{x}" so Bun's FormData multipart boundary matches WebKit (and undici's 4-dash convention) exactly. It also adds two regression tests to test/js/bun/http/form-data-set-append.test.js asserting the declared boundary, body opener, and terminator formats.
I verified the surrounding code: the [70]u8 buffer comfortably holds the new 54-byte string (22-byte prefix + 32 hex chars), and the body-marker emission at lines 603–605 ("--" + boundary + "--\r\n") automatically yields 6-dash markers without further changes. A grep for the old WebkitFormBoundary (lowercase k) shows no other code or tests depend on it; the WebKitFormBoundary references in streams.test.js are hardcoded parser fixtures, unaffected by the generator change.
Security risks
None. The boundary still includes 32 hex chars of UUID-derived randomness, so uniqueness/unpredictability is unchanged. This is purely a cosmetic prefix adjustment for compatibility with picky downstream parsers.
Level of scrutiny
Low. This is a mechanical, well-understood string-literal fix with a clear root cause, a vendored WebKit reference confirming the target format, and gate-checked tests. No CODEOWNERS apply to either touched file.
Other factors
- The only bug-hunter finding is a cosmetic nit: the new tests were inserted between an existing
#12325issue-link comment and its two tests, leaving the comment slightly orphaned. Zero runtime impact; not worth blocking merge. - CI failures were investigated by robobun and are all pre-existing Windows/memory/HMR flakes unrelated to FormData/Blob.
- No prior
claude[bot]reviews on this PR.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@test/js/bun/http/form-data-set-append.test.js`:
- Around line 33-43: The test "FormData via Response uses WebKit-compatible
boundary (`#29630`)" should parse the boundary from contentType (using
res.headers.get("content-type")) and bind the body assertions to that parsed
boundary instead of hardcoding the "------WebKitFormBoundary" prefix; update the
test to extract the boundary token from contentType (the part after
"boundary="), assert the body starts with the exact boundary prefixed by "--"
and also assert the body contains the correct closing delimiter ("--" + boundary
+ "--") so the Response-path framing matches the header-parsed boundary.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 5c1d64b5-c14e-4960-b506-53ef015f372c
📥 Commits
Reviewing files that changed from the base of the PR and between 11231fe3b0a03d6e7cb67b4755c058d0e46b1c22 and b8899515cf0ed3331de08e638e1366585471c7e6.
📒 Files selected for processing (2)
src/bun.js/webcore/Blob.zigtest/js/bun/http/form-data-set-append.test.js
b889951 to
2a1c71b
Compare
There was a problem hiding this comment.
LGTM — one-line format-string fix, buffer still fits (22+32=54 ≤ 70); the earlier test-ordering nit is non-blocking.
Extended reasoning...
Overview
Single production change: src/bun.js/webcore/Blob.zig:588 swaps the multipart boundary format string from "-WebkitFormBoundary{x}" to "----WebKitFormBoundary{x}" (3 extra leading dashes + capital K) so Bun's FormData serialization matches WebKit's exactly. Two regression tests added to test/js/bun/http/form-data-set-append.test.js. Verified the only generation site is this line; other WebKitFormBoundary references in the repo are parser-side test fixtures (test/js/web/streams/streams.test.js) and a doc comment (bun.d.ts), both independent of what Bun emits.
Security risks
None. The boundary is a static prefix + random UUID hex; no user input flows into the format string. The new 54-byte output (22-byte prefix + 32 hex) fits the existing [70]u8 stack buffer with room to spare, so no overflow risk. The downstream framing at lines 603–605/609 already concatenates "--" + boundary and boundary={s} generically, so no length assumptions are violated.
Level of scrutiny
Low. This is a cosmetic/compat tweak to a literal string with no control-flow, allocation, or API-surface changes. The previous value was already RFC-2046-valid (any boundary ≤70 chars works); the fix only aligns dash count and casing with mainstream clients to dodge buggy third-party parsers. CODEOWNERS does not cover these paths.
Other factors
I previously left a 🟡 nit about the new tests being inserted between the #12325 comment header and the tests it labels — that's purely a readability concern in the test file and doesn't affect correctness, so it shouldn't block. CodeRabbit's resolved nit about strengthening the Response-path test is likewise optional (the Request-path test already covers the header↔body binding). CI failures were triaged by robobun as pre-existing Windows/HMR flakes unrelated to FormData/Blob.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
test/js/bun/http/form-data-set-append.test.js (1)
33-43:⚠️ Potential issue | 🟡 MinorStrengthen Response-path framing checks against parsed boundary.
Line 42 only checks a hardcoded prefix. This can miss header/body boundary mismatches; parse
boundaryfromcontent-typeand assert both opening and closing delimiters like the Request test.Suggested patch
test("FormData via Response uses WebKit-compatible boundary (`#29630`)", async () => { const fd = new FormData(); fd.append("field", "value"); const res = new Response(fd); const contentType = res.headers.get("content-type"); expect(contentType).toMatch(/^multipart\/form-data; boundary=----WebKitFormBoundary[0-9a-f]{32}$/); + const boundary = contentType.slice("multipart/form-data; boundary=".length); const body = await res.text(); - expect(body.startsWith("------WebKitFormBoundary")).toBe(true); + expect(body.startsWith(`--${boundary}\r\n`)).toBe(true); + expect(body.endsWith(`\r\n--${boundary}--\r\n`)).toBe(true); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/js/bun/http/form-data-set-append.test.js` around lines 33 - 43, The test "FormData via Response uses WebKit-compatible boundary (`#29630`)" currently only checks that body starts with a hardcoded prefix; instead parse the boundary from the contentType (value in contentType / variable contentType) and use that parsed boundary to assert both the opening delimiter (e.g., `--${boundary}`) appears at the start of body (variable body) and the closing delimiter (`--${boundary}--`) appears at the end, mirroring the Request test behavior; update the assertions to extract boundary from res.headers.get("content-type") and validate opening and closing delimiters consistently rather than using a hardcoded prefix.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@test/js/bun/http/form-data-set-append.test.js`:
- Around line 33-43: The test "FormData via Response uses WebKit-compatible
boundary (`#29630`)" currently only checks that body starts with a hardcoded
prefix; instead parse the boundary from the contentType (value in contentType /
variable contentType) and use that parsed boundary to assert both the opening
delimiter (e.g., `--${boundary}`) appears at the start of body (variable body)
and the closing delimiter (`--${boundary}--`) appears at the end, mirroring the
Request test behavior; update the assertions to extract boundary from
res.headers.get("content-type") and validate opening and closing delimiters
consistently rather than using a hardcoded prefix.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 53e50538-bc4a-4cea-9c95-9673921d5faa
📥 Commits
Reviewing files that changed from the base of the PR and between b8899515cf0ed3331de08e638e1366585471c7e6 and 2a1c71b641093f099cb7976a73bf7565a48f87c6.
📒 Files selected for processing (2)
src/bun.js/webcore/Blob.zigtest/js/bun/http/form-data-set-append.test.js
FormData was generating "-WebkitFormBoundary{hex}" (1 leading dash,
lowercase k), producing 3-dash body markers. WebKit browsers emit
"----WebKitFormBoundary{hex}" (4 dashes, capital K → 6-dash markers)
and undici emits a 4-dash form; Bun's unique shape trips some buggy
downstream multipart parsers (e.g. some OpenAI endpoints).
Change the format string to match WebKit exactly.
Fixes #29630
2a1c71b to
ce16160
Compare
There was a problem hiding this comment.
LGTM — one-line boundary literal fix to match WebKit, with tests; thanks for fixing the comment placement.
Extended reasoning...
Overview
Single-character-class change to a format string literal in src/bun.js/webcore/Blob.zig:588: "-WebkitFormBoundary{x}" → "----WebKitFormBoundary{x}" (4 leading dashes, capital K), aligning Bun's multipart boundary with WebKit's FormDataBuilder.cpp. Two regression tests added to test/js/bun/http/form-data-set-append.test.js. Grep confirms this is the only boundary-generation site in src/.
Security risks
None. The boundary remains derived from a random UUID (16 bytes → 32 hex chars), so entropy is unchanged. The change only adds three dashes and fixes capitalization in a delimiter string — no parsing, auth, or memory-safety implications. Buffer math verified: 22-byte prefix + 32 hex = 54 bytes, well within the existing [70]u8 stack buffer, so the catch unreachable on bufPrint remains sound.
Level of scrutiny
Low. This is a cosmetic interop fix to a string literal with no control-flow changes. The header (content_type at line 609) and body markers (lines 603–605) both reference the same boundary slice, so they stay consistent automatically. The added tests assert the exact format (4-dash declared boundary, 6-dash body markers, correct terminator) for both Request and Response paths.
Other factors
My prior nit (orphaned #12325 issue-link comment) was addressed in ce16160 — the link now sits directly above its two tests. CodeRabbit's trivial nit about tightening the Response-path test is marked resolved; the Request-path test already provides full header/body binding coverage, so the lighter Response test is fine as a supplementary check. CI failures were investigated by the author and are unrelated pre-existing flakes (Windows memory/HMR), none touching FormData/Blob.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
test/js/bun/http/form-data-set-append.test.js (1)
31-41:⚠️ Potential issue | 🟡 MinorBind Response body assertions to the parsed boundary and assert the closing delimiter.
Line 40 currently checks only a hardcoded prefix. This can miss header/body boundary mismatches on the Response path.
🔧 Suggested test tightening
test("FormData via Response uses WebKit-compatible boundary (`#29630`)", async () => { const fd = new FormData(); fd.append("field", "value"); const res = new Response(fd); const contentType = res.headers.get("content-type"); expect(contentType).toMatch(/^multipart\/form-data; boundary=----WebKitFormBoundary[0-9a-f]{32}$/); + const boundary = contentType.slice("multipart/form-data; boundary=".length); const body = await res.text(); - expect(body.startsWith("------WebKitFormBoundary")).toBe(true); + expect(body.startsWith(`--${boundary}\r\n`)).toBe(true); + expect(body.endsWith(`\r\n--${boundary}--\r\n`)).toBe(true); });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/js/bun/http/form-data-set-append.test.js` around lines 31 - 41, The test "FormData via Response uses WebKit-compatible boundary (`#29630`)" currently only asserts a hardcoded prefix; instead parse the actual boundary from contentType (using the same regex that matches /^multipart\/form-data; boundary=(.+)$/ or the existing WebKit regex) and then assert the Response body begins with "--" + boundary and also contains the proper closing delimiter "--" + boundary + "--" (or ends with that closing delimiter possibly preceded by CRLF); update the assertions around contentType and body in this test (variables: fd, res, contentType, body) to extract boundary and assert both opening and closing delimiters match it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@test/js/bun/http/form-data-set-append.test.js`:
- Around line 31-41: The test "FormData via Response uses WebKit-compatible
boundary (`#29630`)" currently only asserts a hardcoded prefix; instead parse the
actual boundary from contentType (using the same regex that matches
/^multipart\/form-data; boundary=(.+)$/ or the existing WebKit regex) and then
assert the Response body begins with "--" + boundary and also contains the
proper closing delimiter "--" + boundary + "--" (or ends with that closing
delimiter possibly preceded by CRLF); update the assertions around contentType
and body in this test (variables: fd, res, contentType, body) to extract
boundary and assert both opening and closing delimiters match it.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1df60629-2e9d-43e0-aa2b-84ac94eb39e5
📥 Commits
Reviewing files that changed from the base of the PR and between 2a1c71b641093f099cb7976a73bf7565a48f87c6 and ce16160.
📒 Files selected for processing (2)
src/bun.js/webcore/Blob.zigtest/js/bun/http/form-data-set-append.test.js
…mory' (#29663) `test/js/bun/net/socket.test.ts` "should not leak memory" has been hitting `TCPSocket`/`TLSSocket` count `4` on the Windows 2019 agents, one past the previous `isWindows ? 3` bound. Same flake shipped with merged #29631 today and has been blocking #29593 through ~15 retriggers. Non-Windows bound stays at `2` — that's where real retention regressions are caught. The Windows bound is already loosened for its shared prototype/structure retention; this just accommodates the one additional retained instance the Win2019 lane is observing. ## Observed - Build [#47600 Win2019 x64](https://buildkite.com/bun/bun/builds/47600#019dbc49-ff0c-49c2-afaa-134711815456) — `Received: 4, Expected: <= 3` - Build [#47616 Win2019 x64](https://buildkite.com/bun/bun/builds/47616#019dbc99-fc61-4111-8170-080d9729866a) — same - Multiple prior PRs: [#29631](#29631), [#29651](#29651), [#29649](#29649), [#29650](#29650), [#29645](#29645), [#29639](#29639) all hit the same pattern ## Scope 3-line diff + comment. If the Win2019 retention count IS a regression worth investigating, that's a separate issue; this change is strictly about not holding other PRs hostage to the shared flake. Co-authored-by: robobun <robobun@bun.sh>
Fixes oven-sh#29630. ## Bug Bun's `FormData` generated a multipart boundary that doesn't match any other mainstream client: | Client | Declared boundary | Body marker | | ----------------- | ------------------------------------------ | ------------------------------------------ | | WebKit browsers | `----WebKitFormBoundary{hex}` (4 dashes) | `------WebKitFormBoundary{hex}` (6 dashes) | | undici | `----formdata-undici-0{N}` (4 dashes) | `------formdata-undici-0{N}` (6 dashes) | | **Bun (before)** | `-WebkitFormBoundary{hex}` (**1 dash**) | `---WebkitFormBoundary{hex}` (**3 dashes**) | Note also the lowercase `k` — WebKit browsers use capital `K`. This unique format triggers bugs in some downstream multipart parsers (the reporter hit a deterministic 400 from OpenAI's `files.create` endpoint on specific binary payloads that uploads fine from Node/undici). ## Cause Single site, `src/bun.js/webcore/Blob.zig:588`: ```zig break :brk std.fmt.bufPrint(&hex_buf, "-WebkitFormBoundary{x}", .{&random}) catch unreachable; ``` ## Fix Change the format string to match WebKit exactly (`vendor/WebKit/Source/WebCore/platform/network/FormDataBuilder.cpp:142` uses `"----WebKitFormBoundary"`): ```zig break :brk std.fmt.bufPrint(&hex_buf, "----WebKitFormBoundary{x}", .{&random}) catch unreachable; ``` The 22-byte prefix + 32 hex chars (54 bytes) still fits in the existing `[70]u8` buffer. The usage sites at lines 603–605 already emit `"--" + boundary + "--\r\n"`, so they automatically produce the correct 6-dash body markers without changes. ## Verification Before: ``` content-type: multipart/form-data; boundary=-WebkitFormBoundary22a1c702... body: ---WebkitFormBoundary22a1c702...\r\n... ``` After: ``` content-type: multipart/form-data; boundary=----WebKitFormBoundary5ec838da... body: ------WebKitFormBoundary5ec838da...\r\n... ``` ## Tests Added two tests to `test/js/bun/http/form-data-set-append.test.js` that assert: - `content-type` matches `^multipart/form-data; boundary=----WebKitFormBoundary[0-9a-f]+$` - body starts with `------WebKitFormBoundary...\r\n` - body ends with `\r\n--<boundary>--\r\n` Gate check: - Stashed src changes → both new tests **fail** (declared boundary has 1 dash, lowercase k). - Restored src changes → all 4 tests in the file **pass**. Co-authored-by: robobun <robobun@users.noreply.github.com>
…mory' (oven-sh#29663) `test/js/bun/net/socket.test.ts` "should not leak memory" has been hitting `TCPSocket`/`TLSSocket` count `4` on the Windows 2019 agents, one past the previous `isWindows ? 3` bound. Same flake shipped with merged oven-sh#29631 today and has been blocking oven-sh#29593 through ~15 retriggers. Non-Windows bound stays at `2` — that's where real retention regressions are caught. The Windows bound is already loosened for its shared prototype/structure retention; this just accommodates the one additional retained instance the Win2019 lane is observing. ## Observed - Build [#47600 Win2019 x64](https://buildkite.com/bun/bun/builds/47600#019dbc49-ff0c-49c2-afaa-134711815456) — `Received: 4, Expected: <= 3` - Build [#47616 Win2019 x64](https://buildkite.com/bun/bun/builds/47616#019dbc99-fc61-4111-8170-080d9729866a) — same - Multiple prior PRs: [oven-sh#29631](oven-sh#29631), [oven-sh#29651](oven-sh#29651), [oven-sh#29649](oven-sh#29649), [oven-sh#29650](oven-sh#29650), [oven-sh#29645](oven-sh#29645), [oven-sh#29639](oven-sh#29639) all hit the same pattern ## Scope 3-line diff + comment. If the Win2019 retention count IS a regression worth investigating, that's a separate issue; this change is strictly about not holding other PRs hostage to the shared flake. Co-authored-by: robobun <robobun@bun.sh>
Fixes oven-sh#29630. ## Bug Bun's `FormData` generated a multipart boundary that doesn't match any other mainstream client: | Client | Declared boundary | Body marker | | ----------------- | ------------------------------------------ | ------------------------------------------ | | WebKit browsers | `----WebKitFormBoundary{hex}` (4 dashes) | `------WebKitFormBoundary{hex}` (6 dashes) | | undici | `----formdata-undici-0{N}` (4 dashes) | `------formdata-undici-0{N}` (6 dashes) | | **Bun (before)** | `-WebkitFormBoundary{hex}` (**1 dash**) | `---WebkitFormBoundary{hex}` (**3 dashes**) | Note also the lowercase `k` — WebKit browsers use capital `K`. This unique format triggers bugs in some downstream multipart parsers (the reporter hit a deterministic 400 from OpenAI's `files.create` endpoint on specific binary payloads that uploads fine from Node/undici). ## Cause Single site, `src/bun.js/webcore/Blob.zig:588`: ```zig break :brk std.fmt.bufPrint(&hex_buf, "-WebkitFormBoundary{x}", .{&random}) catch unreachable; ``` ## Fix Change the format string to match WebKit exactly (`vendor/WebKit/Source/WebCore/platform/network/FormDataBuilder.cpp:142` uses `"----WebKitFormBoundary"`): ```zig break :brk std.fmt.bufPrint(&hex_buf, "----WebKitFormBoundary{x}", .{&random}) catch unreachable; ``` The 22-byte prefix + 32 hex chars (54 bytes) still fits in the existing `[70]u8` buffer. The usage sites at lines 603–605 already emit `"--" + boundary + "--\r\n"`, so they automatically produce the correct 6-dash body markers without changes. ## Verification Before: ``` content-type: multipart/form-data; boundary=-WebkitFormBoundary22a1c702... body: ---WebkitFormBoundary22a1c702...\r\n... ``` After: ``` content-type: multipart/form-data; boundary=----WebKitFormBoundary5ec838da... body: ------WebKitFormBoundary5ec838da...\r\n... ``` ## Tests Added two tests to `test/js/bun/http/form-data-set-append.test.js` that assert: - `content-type` matches `^multipart/form-data; boundary=----WebKitFormBoundary[0-9a-f]+$` - body starts with `------WebKitFormBoundary...\r\n` - body ends with `\r\n--<boundary>--\r\n` Gate check: - Stashed src changes → both new tests **fail** (declared boundary has 1 dash, lowercase k). - Restored src changes → all 4 tests in the file **pass**. Co-authored-by: robobun <robobun@users.noreply.github.com>
…mory' (oven-sh#29663) `test/js/bun/net/socket.test.ts` "should not leak memory" has been hitting `TCPSocket`/`TLSSocket` count `4` on the Windows 2019 agents, one past the previous `isWindows ? 3` bound. Same flake shipped with merged oven-sh#29631 today and has been blocking oven-sh#29593 through ~15 retriggers. Non-Windows bound stays at `2` — that's where real retention regressions are caught. The Windows bound is already loosened for its shared prototype/structure retention; this just accommodates the one additional retained instance the Win2019 lane is observing. ## Observed - Build [#47600 Win2019 x64](https://buildkite.com/bun/bun/builds/47600#019dbc49-ff0c-49c2-afaa-134711815456) — `Received: 4, Expected: <= 3` - Build [#47616 Win2019 x64](https://buildkite.com/bun/bun/builds/47616#019dbc99-fc61-4111-8170-080d9729866a) — same - Multiple prior PRs: [oven-sh#29631](oven-sh#29631), [oven-sh#29651](oven-sh#29651), [oven-sh#29649](oven-sh#29649), [oven-sh#29650](oven-sh#29650), [oven-sh#29645](oven-sh#29645), [oven-sh#29639](oven-sh#29639) all hit the same pattern ## Scope 3-line diff + comment. If the Win2019 retention count IS a regression worth investigating, that's a separate issue; this change is strictly about not holding other PRs hostage to the shared flake. Co-authored-by: robobun <robobun@bun.sh>
Fixes #29630.
Bug
Bun's
FormDatagenerated a multipart boundary that doesn't match anyother mainstream client:
----WebKitFormBoundary{hex}(4 dashes)------WebKitFormBoundary{hex}(6 dashes)----formdata-undici-0{N}(4 dashes)------formdata-undici-0{N}(6 dashes)-WebkitFormBoundary{hex}(1 dash)---WebkitFormBoundary{hex}(3 dashes)Note also the lowercase
k— WebKit browsers use capitalK.This unique format triggers bugs in some downstream multipart parsers
(the reporter hit a deterministic 400 from OpenAI's
files.createendpoint on specific binary payloads that uploads fine from Node/undici).
Cause
Single site,
src/bun.js/webcore/Blob.zig:588:Fix
Change the format string to match WebKit exactly
(
vendor/WebKit/Source/WebCore/platform/network/FormDataBuilder.cpp:142uses
"----WebKitFormBoundary"):The 22-byte prefix + 32 hex chars (54 bytes) still fits in the existing
[70]u8buffer. The usage sites at lines 603–605 already emit"--" + boundary + "--\r\n", so they automatically produce the correct6-dash body markers without changes.
Verification
Before:
After:
Tests
Added two tests to
test/js/bun/http/form-data-set-append.test.jsthat assert:
content-typematches^multipart/form-data; boundary=----WebKitFormBoundary[0-9a-f]+$------WebKitFormBoundary...\r\n\r\n--<boundary>--\r\nGate check: