Blob: keep File and FormData entry names on the Blob instead of the shared store - #37680
Blob: keep File and FormData entry names on the Blob instead of the shared store#37680robobun wants to merge 7 commits into
Conversation
…ed store new File([blob], name) shares the source blob's byte store and wrote the name into it, renaming the source (and anything else sharing the store). It also freed the previous name under FormData entries, which hold a zero-copy view of the name they read at append() time. Blob__setAsFile wrote FormData entry filenames into the same shared store, and lost them entirely for zero-byte blobs, which have no store. The File constructor and Blob__setAsFile now set the per-Blob name. The consumers that read a name for something other than the .name getter (FormData's default filename, the blob: loader, Bun.serve's Content-Disposition) go through the same per-Blob-first lookup the getter uses. The store's stored_name is only written when a store is created. The File constructor also converts the name as a USVString, which the old UTF-8 copy did implicitly for byte-backed Files but not for file-backed ones.
|
Warning Review limit reached
Next review available in: 7 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (7)
Comment |
|
Updated 5:56 AM PT - Aug 12th, 2026
✅ @robobun, your commit 0aa2f22e1afc18b7eb151a16a8b5c22b59d1c349 passed in 🧪 To try this PR locally: bunx bun-pr 37680That installs a local version of the PR into your bun-37680 --bun |
|
Status: ready for review. Reproduced on 1.4.0 and on a debug build of main with the scripts in the description: the FormData one is a heap-use-after-free under ASAN ( |
There was a problem hiding this comment.
I reviewed this PR and didn't find any bugs. Because it reworks name ownership across the Rust/C++ FFI boundary for a shared refcounted type and carries several user-visible behavior changes that interact with other open PRs, a human look would still be worthwhile.
What was reviewed:
Blob__getFileNameString/Blob__setAsFilelifetime contracts against their C++ callers inblob.cppandJSDOMFormData.cpp—dupe_ref()andtoWTFString(ZeroCopy)each take their own +1, andBlob::createdupes at append time sosetAsFilewrites to the entry's own Blob.get_name_utf8()'s returnedZigStringSliceis self-owning (to_utf8()is the ref-holding variant), soRequestContextandget_loaderdon't borrow pastself.name.OwnedString::into_inner→OwnedStringCell::settransfers the +1 without leak or double-deref; the UTF-16 USVString reassignment drops the oldOwnedString.init_empty_with_namehas no remaining callers; the two intentionalget_file_name()sites injsc_hooks.rsare guarded byneeds_to_read_file().
Extended reasoning...
Overview
This PR fixes a heap-use-after-free and a spec-correctness bug in File/FormData name handling by moving the per-Blob name off the shared store::Bytes::stored_name and onto Blob::name. It touches src/jsc/webcore_types.rs (comments, removes init_empty_with_name), src/runtime/webcore/Blob.rs (jsdom_file_construct, Blob__setAsFile, Blob__getFileNameString, new get_name_utf8 helper, Any::get_name_utf8), src/runtime/server/RequestContext.rs (Content-Disposition filename lookup), and adds 19 tests across blob.test.ts and FormData.test.ts.
Security risks
None identified. The change removes the only site that reassigned a live shared store's stored_name (the root cause of the borrowed-view UAF), and the new Blob__getFileNameString returns a ref-counted WTF string rather than a raw byte view. The Content-Disposition path still goes through the existing contains_any(.., b"\r\n\0\"") injection guard.
Level of scrutiny
High. This is native memory-management code in a type shared across the Rust/C++ FFI boundary (Blob__setAsFile, Blob__getFileNameString, Blob__dupe) with intrusive refcounting on both the Blob and its Store. I traced the ownership of every string handoff: jsdom_file_construct uses OwnedString::into_inner() → OwnedStringCell::set() (transfers the +1, derefs the old value); the UTF-16 branch's reassignment drops the old OwnedString before adopting the clone_utf8 result; Blob__setAsFile's dupe_ref() bumps the WTF ref so the name outlives the C++ stack local; Blob__getFileNameString returns a borrowed BunString that the C++ caller immediately toWTFString(ZeroCopy)s into its own ref while this is still live. get_name_utf8() returns a ZigStringSlice from the ref-holding to_utf8() (not _without_ref / _borrowed), so callers in RequestContext.rs and get_loader hold a self-contained slice that doesn't depend on self.name staying put. I also confirmed Blob::create(JSValue) in blob.h calls Blob__dupeFromJS, so the FormData entry's impl() is a distinct Blob from the user's — writing name on it via Blob__setAsFile does not touch the appended blob, and dupe_with_content_type's name: self.name.clone() (which dupe_refs) carries it to the JS wrapper.
Other factors
The PR carries four intentional behavior changes beyond the bug fix (new File(bits, "").name → "", FormData no longer naming the appended blob, zero-byte entries keeping their filename, and Bun.serve's Content-Disposition preferring the File's display name over the on-disk basename). All match Node per the description, and each is covered by a new test, but they are user-visible and the description names four other open PRs (#33603/#33604/#35079/#35523/#36435) that touch the same two functions or depend on this change's semantics. That coordination is exactly what a maintainer should sign off on. Test coverage is thorough (variant matrix over source kinds, structuredClone, USVString, blob: loader, Content-Disposition, spawned ASAN repro for the UAF), and the description states 18/19 fail on main and all pass with the change; CI is still building.
…me string A WTF::StringImpl becomes an atom of whichever thread first uses it as a property key, and dropping its last reference on another thread aborts in AtomStringImpl::remove. The registry shared the registered blob's name impl with every thread that resolved the URL. That was already reachable for file-backed Files and now, with names kept on the Blob, for every File, so copy the name when storing the entry and again on each resolve.
There was a problem hiding this comment.
Automated review ran and found no bugs. This touches memory-safety-critical native code (WTF string refcounting across the Rust/C++ FFI boundary, cross-thread StringImpl handling in ObjectURLRegistry) and changes user-visible behavior in several documented places, so a human look is still worthwhile.
What was reviewed:
Blob__getFileNameStringnow returns a borrowedBunStringview ofblob.name; verifiedtoWTFString(ZeroCopy)at both C++ call sites (JSDOMFormData.cpp:315,478) refs the impl viaWTF::String(impl.wtf), so the borrowed return is sound.dupe_with_private_namerefcount balance:replace→into_innertransfers the +1,String::to_thread_safeswaps the impl in place,setinstalls it over thedead()placeholder — no leak or double-free.- Remaining
get_file_name()callers (jsc_hooks.rs:1506,4244) are the on-disk-path uses the description says are intentional;init_empty_with_namehas no callers left.
Extended reasoning...
Overview
Moves File / FormData-entry names from the shared store::Bytes::stored_name onto the per-Blob name field, fixing a class of bugs where wrapping a Blob in new File([blob], name) or appending it to FormData renamed the source (and could UAF a borrowed name view held by FormData). Also fixes a pre-existing cross-thread AtomStringImpl release abort in ObjectURLRegistry by giving the registry entry and each resolver a private (isolated-copy) name string. Touches jsdom_file_construct, Blob__setAsFile, Blob__getFileNameString, get_loader, Any::get_file_name→get_name_utf8, RequestContext Content-Disposition, and adds 21 tests across two files.
Security risks
None identified. The change removes a heap-use-after-free reachable from user JS and a cross-thread abort. The Content-Disposition path already sanitizes control bytes (contains_any(.., b"\\r\\n\\0\\\"")) and truncates to a stack buffer; switching from store path to per-Blob name doesn't widen that surface.
Level of scrutiny
High. This is native code at the Rust↔C++ FFI boundary manipulating WTF StringImpl refcounts, changes cross-thread string ownership semantics in a process-global registry, and intentionally alters user-visible behavior (new File(bits, "").name, FormData not renaming the appended blob, zero-byte parts keeping their filename, Bun.serve Content-Disposition preferring the File's display name over the on-disk basename). The reasoning is careful and the test matrix is thorough (18/21 fail on main, spawned ASAN repros for the UAF and the atom-release abort, worker handshake for the cross-thread case), but the combination of refcount transfers, borrowed-return FFI contracts, and thread-affinity invariants is exactly the category REVIEW.md flags as most-blocked.
Other factors
I traced the borrowed-BunString contract of Blob__getFileNameString through BunString.cpp's toWTFString(ZeroCopyTag) → WTF::String(this->impl.wtf) and confirmed it refs the impl, so the C++ FormData filename outlives any later reassignment of blob.name. OwnedStringCell::set derefs the previous value, so Blob__setAsFile overwriting the entry-dupe's name and jsdom_file_construct overwriting the source-dupe's name don't leak. dupe_with_private_name's replace(dead) → into_inner → to_thread_safe → set sequence balances to exactly one +1 on the isolated copy. The Any::get_file_name → get_name_utf8 rename's only in-tree caller is the RequestContext site updated in this diff. All comment-cop threads are resolved (comments shortened in de3f1c9/ec05e1e). Given the scope and the number of intentional behavior changes interacting with several other open PRs (#33603/#33604/#35079/#35523/#36435/#31487), a maintainer should confirm the layering choice and the Content-Disposition behavior change.
The loader and Content-Disposition lookups borrow the store's name when the blob has none of its own, as they did before, instead of allocating a cached copy per response body. Pin both fallbacks with a Bun.file() case.
There was a problem hiding this comment.
I reviewed this PR and the automated bug hunt found no issues. Because it reworks name ownership across the Blob/Store boundary (a heap-use-after-free fix), changes cross-thread WTF::StringImpl handling in ObjectURLRegistry, and alters several user-visible behaviors while intersecting with at least six other open PRs, a maintainer look is still worthwhile.
What was reviewed:
Blob__getFileNameStringnow returningget_name_string()— checked the C++ callers inJSDOMFormData.cppstill ref viatoWTFString(ZeroCopy), so the borrowed contract holds.Blob__setAsFilewritingthis.nameand bothblob.cpptoJSpaths — the entry's dupe inherits the name viadupe_with_content_type'sname.clone().get_name_utf8not caching — the store-backed branch returns anever_freeview of bytes the store owns, matching the priorget_file_name()lifetime at the two rewritten call sites.dupe_with_private_name—to_thread_safe()on both registry entry and each resolve, so no thread's atom impl is shared.
Extended reasoning...
Overview
This PR moves File/FormData-entry names off the shared refcounted Store and onto the per-Blob name field. It touches four Rust files (webcore_types.rs, Blob.rs, ObjectURLRegistry.rs, RequestContext.rs) and adds 21 tests across two test files. The core changes are: jsdom_file_construct now sets blob.name unconditionally instead of mutating store::Bytes::stored_name; Blob__setAsFile writes the per-Blob name; Blob__getFileNameString and a new get_name_utf8 helper use the per-Blob lookup; ObjectURLRegistry copies the name via to_thread_safe() on both register and resolve; init_empty_with_name is deleted as dead.
Security risks
None in the classic sense (no auth/injection surface), but this is squarely in the memory-safety category the repo review guide calls out as most-blocked: it fixes a heap-use-after-free (freed stored_name bytes read by FormData serialization) and a cross-thread RELEASE_ASSERT in AtomStringImpl::remove. The fix reasoning depends on WTF string atom semantics, BunString ref ownership across the FFI boundary, and OwnedStringCell::clone behavior in dupe_with_content_type.
Level of scrutiny
High. This is native code changing ownership of a string field that crosses Rust↔C++ FFI (Blob__setAsFile, Blob__getFileNameString) and is shared across threads via ObjectURLRegistry. It also introduces four user-visible behavior changes (empty-name File returns "", FormData no longer renames the appended blob, zero-byte parts keep filenames, Bun.serve Content-Disposition uses the File's display name), and the description explicitly maps interactions with #31487, #33603, #33604, #35079, #35523, #36001, and #36435 — coordination a maintainer should sign off on.
Other factors
The PR is exceptionally well-documented and well-tested (18/21 new tests fail on main, spawned ASAN repros for both memory bugs, matrix coverage of the variant space). The comment-cop feedback was addressed in de3f1c9/ec05e1e. I traced the C++ callers of the two changed extern "C" functions and the dupe→name.clone() path and found nothing wrong, but the combination of FFI ownership, cross-thread atom-string semantics, and multi-PR coordination puts this outside what an automated review should approve on its own.
The blob: loader and FormData's default filename now follow a File's own name for a File wrapping a Bun.file() and for a blob named through the setter, and a FormData filename override applies to an already named File. Add tests for each, and note at the two module-loading sites that the path they read from the store is intentionally not the File's name.
There was a problem hiding this comment.
I reviewed this PR and the automated bug hunt found no issues. Because it reworks the Blob/File name ownership model across the FFI boundary (WTF string refcounts at Blob__getFileNameString/Blob__setAsFile) and adds cross-thread StringImpl isolation in the process-global ObjectURLRegistry, a human look at the refcount and thread-safety reasoning would be worthwhile.
What was reviewed:
get_name_utf8lifetimes at the three call sites (RequestContext Content-Disposition,get_loader,Any) — theZigStringSliceis consumed before drop.Blob__getFileNameString→toWTFString(ZeroCopy)takes its own +1; the returnedBunStringborrowsself.namewithout an extra ref, so no leak.dupe_with_private_name:replace(dead) → to_thread_safe → setkeeps the count balanced (BunString__toThreadSafederefs the original when it swaps in the isolated copy); Dead/non-WTF tags pass theis_thread_safedebug assert.- The two remaining
get_file_name()callers injsc_hooks.rsintentionally read the store path;init_empty_with_namehas no remaining callers.
Extended reasoning...
Overview
This PR moves File/FormData-entry names from the shared Store::Bytes::stored_name onto the per-Blob name field, fixing (1) new File([blob], name) renaming every Blob that shares the store, (2) a heap-use-after-free where FormData's borrowed default filename was freed by a later new File, and (3) a pre-existing cross-thread AtomStringImpl::remove abort in ObjectURLRegistry that this change would otherwise widen. It touches jsdom_file_construct, Blob__setAsFile, Blob__getFileNameString, adds BlobExt::get_name_utf8, rewires three name consumers (Content-Disposition, get_loader, Any::get_name_utf8), and adds dupe_with_private_name to the object-URL registry's store and resolve paths. 25 new tests cover the matrix.
Security risks
The change removes a user-reachable UAF and a cross-thread abort; it does not add attack surface. The Content-Disposition path already sanitizes against header injection.
Level of scrutiny
High. This is native memory-safety code at the Rust/C++ FFI boundary: WTF StringImpl refcounting at Blob__getFileNameString (borrowed BunString consumed by toWTFString(ZeroCopy)), OwnedStringCell::set/replace balancing in Blob__setAsFile and dupe_with_private_name, and isolatedCopy()-based thread isolation in a process-global mutex-guarded registry. REVIEW.md flags every one of these categories (borrowed views, provably balanced refcounts on every path, thread affinity of shared strings) as most-blocked. My trace of each path looks balanced, but this is exactly where a maintainer familiar with the WTF atom model and the overlapping PRs (#31487, #35079, #35523, #36435) should confirm the design.
Other factors
The description enumerates every intentional behavioral change and pins each with a test; 23/25 fail on main. The comment-cop feedback was addressed in de3f1c9/ec05e1e. CI on the prior head was green modulo two known-unrelated flakes. The change also intersects with several open/planned PRs on the same functions, which argues for a human coordinating the merge order.
Problem
new File([a], "b.txt")renamesaas well:a.namebecomes"b.txt"(Node and browsers keep"a.txt").fd.append("f", blob, "entry.txt")likewise names the caller'sblob, and a zero-byte blob appended with a filename reports no name at all.new File([file], other)reads freed memory. A debug build reportsAddressSanitizer: heap-use-after-freeinFormDataContext::on_entry, freed byjsdom_file_construct.new File([blob], name)shares the source's byte store, and the byte-backed constructor and FormData entry naming both wrote the name into that shared store. That renamed every Blob on the store and freed the old name buffer FormData still borrowed. File- and S3-backed Files already kept the name on the Blob.ASSERTION FAILED: wasRemoved (AtomStringImpl.cpp:462). On 1.4.0 this needed a file-backed File.Fix
blob:imports, theContent-DispositionfilenameBun.serveadds) now use the.namelookup, so a File wrapping aBun.file()goes by its display name everywhere and a plainBun.file()still uses its path. The resulting behavior changes are listed in the original below.Background
Blobis a window (offset, size) onto a refcounted store holding bytes, a file path, or an S3 key.new File([blob], name)andblob.slice()share the source's store on purpose; only the Blob-level fields differ..namegetter prefers the per-Blob field, so the store's name is only a fallback.URL.createObjectURLhands the registered blob to whichever thread resolves the URL, workers included.Original description
Repro
The rename also reaches memory that is still in use.
FormData.append(name, file)takes the default filename fromBlob__getFileNameString, a borrowed view of the store's name bytes that the C++ side wraps without copying. A laternew File([file], other)freed those bytes, so serializing the FormData read freed memory. Released build:Debug build of main on the same script:
Cause
A
Blobis a window (store,offset,size) onto a refcountedStore, andnew File([blob], name)shares the part's store rather than copying it.jsdom_file_constructstored the new name instore::Bytes::stored_name, i.e. on the object shared with the source, so every Blob viewing that store was renamed and the previous name buffer was freed under any FormData entry still pointing at it.Blob__setAsFile(which attaches a FormData entry's filename when the entry is converted to JS) wrote into the shared store the same way, and dropped the name when the blob had no store, which is the case for every zero-byte blob.The store already has no say in a File's name for file- and S3-backed Files: that arm of the constructor set the per-Blob
namefield, andget_name_string()(the.namegetter) prefers that field over the store's name. The byte-backed arm was the odd one out.Fix
jsdom_file_constructsetsblob.namefor every kind ofbits, and no longer creates an empty store whose only purpose was to carry the name of an empty File. Names are now kept as a WTF string instead of a UTF-8 copy, so the constructor converts them as a USVString explicitly; the UTF-8 copy used to do that implicitly for byte-backed Files, and file-backed Files (new File([Bun.file(p)], "a\uD800b")) did not get it at all.Blob__setAsFilesets the per-Blob name on the entry's own dupe. The store is not touched, and a storeless (zero-byte) blob keeps its name too.Blob__getFileNameString(FormData's default filename),get_loader(blob:imports pick the loader from the File's name), and theContent-DispositionfilenameBun.serveadds tonew Response(file). Without this, those three would stop seeing File names once the constructor no longer writes them into the store.BlobExt::get_name_utf8is the shared helper;AnyBlob::get_file_namebecomesget_name_utf8on top of it. It does not go through the getter's caching: a blob without a name of its own (aBun.file()response body) still borrows the store's path exactly as before, so serving files does not gain an allocation; a File's own ASCII name is a ref-holding view, also allocation free.store::Bytes::init_empty_with_nameloses its last caller and is removed.stored_nameis now only written when a store is created (standalone executables' embedded files, structured clone) and serves as the fallback for blobs with no name of their own.The two
blob:module-loading sites injsc_hooks.rsandbundler/options.rskeep using the store-levelget_file_name()on purpose: they use it as the path to read when the blob is aBun.file(), which must stay the on-disk path even when the File was given a different display name.Why this is the right place rather than copying the bytes on
new File([blob]): sharing the store is deliberate (and #36001 shares even more), and every consumer of the name already had a per-Blob field to read; the bug was the one writer that bypassed it. It also removes the only code that reassigns a live store'sstored_name, which is what made the borrowedBlob__getFileNameStringview unsound.Object URLs and other threads
Keeping the name on the Blob as a
WTF::StringImpl(instead of a byte copy in the store) exposed a second pre-existing bug, so this PR also fixes it.ObjectURLRegistrystored adupe()of the registered blob and handeddupe()s of that entry to whichever thread resolved the URL, so all of them shared one name impl. A string that a thread has used as a property key is an atom of that thread's string table (WTF atomizes the impl in place), and releasing the last reference to it from another thread hitsRELEASE_ASSERTinAtomStringImpl::remove. On 1.4.0 this was reachable with a file-backed File, whose name already lived on the Blob:With this change the same would have applied to every File, so
Entry::initandresolve_and_dupenow both go throughdupe_with_private_name, whichto_thread_safe()s (copies) the name: the entry's impl is never reachable from any thread's JS, and each resolving thread gets its own. Copying on both sides is what makes it safe; a plain impl handed to a worker could still be atomized there and then released last by the registering thread. #31487 does the same fornameas part of a larger change that also snapshots the store contents; this is just the part this PR needs.Behavior changes besides the bug itself. The first four match Node; the rest are the places where Bun used to consult the store's name and now consult the same name
.namereports, so a File wrapping aBun.file(), or a blob named through Bun's.namesetter, is treated according to that name everywhere. Each has a test:new File(bits, "").nameis""(wasundefined).fd.append(name, blob, filename)/fd.set(...)no longer name the blob that was passed in.undefined). This is the bug FormData: preserve filename when parsing a zero-byte multipart file part #36435 fixes by creating a named store inBlob__setAsFile; with this change that store is not needed.fd.append(name, file, filename)/seton a File that already has a name:get()now returns a File namedfilename, as the multipart body already said (it returned the File's original name; this is FormData: honor filename override in append/set when value is already a File #35079's bug).new File([Bun.file(p)], "display.bin")isdisplay.binin the multipart body as well (the body used to carry the full path ofpwhileget().namesaiddisplay.bin), and a blob given a name through the.namesetter gets that as its default filename (the body used to carryfilename=""). A plainBun.file(p)entry still defaults topas before (FormData: send only basename, not full path, as Bun.file() multipart filename #35523 wants that to become the basename; it would apply on top of the new lookup).Bun.serveresponding withnew File([Bun.file(p)], "display.zip")now advertisesfilename="display.zip"instead of the basename ofp; a plainBun.file(p)body still advertises the basename ofp.blob:import orWorkerofnew File([Bun.file("x.ts")], "x.txt")now picks the loader fromx.txt(text) while still reading the bytes fromx.ts; it used to pick it from the path. A plainBun.file()object URL is unchanged. The two module-loading sites that need the on-disk path keep reading it from the store and now say so.Not observable from tests here but following from the same rule: a standalone executable's embedded files keep their names when wrapped in a
new File()(the wrapper used to rename the embedded blob's store).Unchanged here and left to their own PRs: a single-Blob part still hands its
name/is_jsdom_fileto the wrapper viadupe()(#33603), andfile.slice()still reports the File's name (#33604). This change is what makes the store stop leaking names into those wrappers once they stop copying metadata. #35079 (Blob__setAsFilehonoring a filename override) and #35523 (Blob__getFileNameStringreturning a basename forBun.file) touch the same two functions; see the list above for how each case comes out here.Verification
New tests in
test/js/web/fetch/blob.test.ts(13 under "new File([blob], name) names only the new File", plus 2 under "a File's name reaches other threads through an object URL as a private copy") andtest/js/web/html/FormData.test.ts(10, under "entry filenames belong to the entry ..."). They cover: File, empty File, Blob, slice, and chained sources; the source's name being read before wrapping (thedupe()d name used to win); structuredClone; the empty name; USVString conversion; theblob:loader andContent-Disposition, each for byte-backed Files, for a File wrapping aBun.file(), and (to pin the unchanged fallback) for a plainBun.file(); the freed FormData filename (in a spawned process, so ASAN fails the test rather than the run); the object URL scenario above for a byte-backed and a file-backed File (spawned, handshaking with the worker over postMessage so the worker provably holds the last reference); and on the FormData sideappend/setwith a filename on a Blob and on an already named File, zero-byte blobs (appended and parsed), default filenames of wrapped Files, of a File wrapping aBun.file()and of a setter-named blob in bothget()and the multipart body, and wrapping a parsed entry.Of the 25, 23 fail on 1.4.0 and on a debug build of main (the file-backed object URL case by aborting); the byte-backed object URL case passes there and aborts with only the first part of this change applied, and the "wrapping an empty Blob" row passes either way and is there for the matrix. All pass with the change.
blob.test.ts,FormData.test.ts,FormData-multipart-serialization.test.ts,blob-file-name-ownership.test.ts,blob-array-fast-path.test.ts,structured-clone-blob-file.test.ts,structuredClone-classes.test.ts,globals.test.js,worker_blob.test.ts,buffer-resolveObjectURL.test.ts,body.test.ts,response.test.ts,bun-serve-file.test.ts,fetch-file-upload.test.tsand node'stest-blob-createobjecturl.jspass on the debug build (response.test.ts's "handle stack overflow" times out there on main as well, without this change).