webcore: File.prototype.slice() returns a plain Blob, not a File - #33604
webcore: File.prototype.slice() returns a plain Blob, not a File#33604robobun wants to merge 5 commits into
Conversation
WalkthroughBlob creation paths now clear File identity fields, while name retrieval suppresses stored names for plain byte-backed Blobs. Tests cover slicing, Blob wrapping, multipart naming, and preservation of the original File. ChangesBlob File Identity Stripping
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
Updated 7:16 PM PT - Jul 10th, 2026
❌ @robobun, your commit 33ed42b has 3 failures in
🧪 To try this PR locally: bunx bun-pr 33604That installs a local version of the PR into your bun-33604 --bun |
|
Found 1 issue this PR may fix:
🤖 Generated with Claude Code |
|
This does not fix #20700. That issue is about the |
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
|
Not a duplicate of #33603. That PR fixes the Overlap: both end up fixing |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/runtime/webcore/Blob.rs`:
- Around line 2190-2196: `get_name_string` and `Blob__getFileNameString`
duplicate the same file-identity suppression logic, so extract that predicate
into a shared private `Blob` helper such as `suppresses_stored_name` and have
both call sites use it. Keep the helper encapsulating the `is_jsdom_file` and
`store.data` check so any future refinement only needs to be updated in one
place.
In `@test/js/web/fetch/blob.test.ts`:
- Line 222: The assertion in the blob slicing test is too weak because it only
checks that lastModified is not the old value instead of verifying the
normalized Blob behavior. Update the expectation in the blob slice test around
sliced so it asserts the actual value of lastModified is undefined, matching the
fact that the result is a plain Blob and not a File. Keep the check anchored to
the sliced blob object and its lastModified property.
🪄 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: f1e3f85a-10dc-491c-ad40-6feeb76c3474
📒 Files selected for processing (2)
src/runtime/webcore/Blob.rstest/js/web/fetch/blob.test.ts
|
CI status: the diff is green on all 280 lanes that ran. The two red jobs are both Ready for review. |
ac72fd5 to
2aaa634
Compare
|
CI on 05cd74f (build 70174):
All review threads resolved. Ready for review. |
Per the File API spec, Blob.prototype.slice() constructs a new plain Blob regardless of the receiver's subclass. Bun was duping the receiver and carrying over is_jsdom_file, name and last_modified, so slicing a File yielded a File that answered true to instanceof File and inherited the parent's name and lastModified. The same propagation happened for new Blob([file]). Clear the File brand and identity fields on the dupe produced by slice() and by the Blob constructor, and gate the .name getter so it does not surface the shared Bytes store's stored_name on a non-File Blob.
…t File's name for a slice
…ng Bun.file() does not unmask the disk path
05cd74f to
33ed42b
Compare
There was a problem hiding this comment.
No new issues found; the earlier regression I flagged (File-store slice unmasking the disk path) is fixed and covered by the new test. Deferring to a human because this is a user-visible Web API behavior change with acknowledged collateral (Bun.embeddedFiles FormData filename now empty) and it overlaps with #33603/#32434 at the same layer — worth a maintainer deciding how those compose.
Checked: get_slice_from / Blob constructor clear is_jsdom_file/last_modified and gate name clearing on Bytes-only stores; ordering after is_jsdom_file.set(false) makes the predicate reduce to the store-type check as intended.
Checked: empty-File .slice() early-return path (get_slice:2055) returns a fresh init_empty blob, so the new code is bypassed there — matches the test's toBeUndefined().
Checked: get_name_string / Blob__getFileNameString both route through the shared hides_bytes_stored_name() helper; structuredClone path via Blob__dupe still copies is_jsdom_file.
Remaining pre-existing sibling consumers (Any::get_file_name for Content-Disposition, Blob__setAsFile for FormData read-back) were noted inline and deferred to #32430/#32434.
Extended reasoning...
Overview
Three files: src/jsc/webcore_types.rs (adds hides_bytes_stored_name() helper), src/runtime/webcore/Blob.rs (clears File identity in get_slice_from and the Blob constructor; gates the .name getter and Blob__getFileNameString FFI on the new helper), and test/js/web/fetch/blob.test.ts (~80-line test covering slice, wrap, FormData encode, structuredClone, and the Bun.file()-backed File regression case).
Security risks
None introduced. The change is a spec-compliance fix that reduces metadata leakage (parent File name no longer propagates to derived slices). One regression I flagged earlier — where clearing blob.name on a File/S3-backed DOM File slice unmasked the on-disk path via get_file_name() fallthrough — has been fixed by gating the clear on hides_bytes_stored_name() (Bytes-store only), with a regression test.
Level of scrutiny
Medium-high. Blob.rs is core webcore runtime code with many downstream consumers (FormData encode/decode, Bun.serve Content-Disposition, structuredClone, embedded-file assets, S3). The stored_name field on the shared Bytes store has at least four independent readers, and the new predicate interacts differently with each of the three store variants. During review this PR already went through one 🔴 regression fix and two acknowledged-as-follow-up behavior notes, which is a signal that the interaction surface is non-trivial.
Other factors
- Overlap with open PRs: #33603 fixes
new Blob([file]) instanceof Filevia a different layer (dupe_without_metadata()insideBlob::get), and #32430/#32434 restructure the File prototype chain more broadly. A maintainer should decide sequencing/composition. - Acknowledged collateral:
fd.append(name, Bun.embeddedFiles[i])in--compile'd binaries now emitsfilename=""instead of the internal/$bunfs/root/...path. Author agreed athis.nameshort-circuit inBlob__getFileNameStringwould be strictly better but deferred it; a human should confirm that trade-off is acceptable to ship. - CI: green on all lanes that ran; failures are unrelated infra (artifact download timeout, MySQL/Postgres container health).
- All four of my prior inline threads are resolved; the bug-hunting pass this run found nothing new.
Repro
Per the File API slice algorithm,
slice()on a File constructs a new plainBlob, with no name, no lastModified, and not aFile. Bun returned aFilecarrying the parent's identity.x instanceof Fileis the standard branch form/upload handlers and FormData encoders use to decide whether to emit afilename=, so every byte range sliced from a File took that branch and leaked the parent's name onto arbitrary derived fragments.The same propagation happened for
new Blob([file]), which also answeredtruetoinstanceof File.Cause
get_slice_fromand theBlobconstructor both build the result viadupe(), which copiesis_jsdom_file,nameandlast_modifiedfrom the source.instanceof Fileis implemented asis_jsdom_file.get(), so the slice answered as a File.The name additionally lives on the shared
Bytesstore asstored_name(set by the File constructor), and the.namegetter fell through to it regardless of the receiver's File brand, so even after clearingis_jsdom_filethe slice still reported the parent's name.Fix
In
get_slice_fromand theBlobconstructor, clearis_jsdom_file,last_modifiedandnameon the duped result so it is a plain Blob.In
get_name_string, skip theBytes.stored_namefallback when the receiver is not a DOM File. This keepsBun.file(path).nameand S3.name(non-Bytes stores) working while preventing the shared store's DOM File name from leaking onto plain Blobs that reference it.structuredCloneof a File is unaffected: it goes throughBlob__dupe, which still copiesis_jsdom_file.Verification
Related: #32430 and #32434 restructure the File prototype chain more broadly; this PR is the minimal behavioral fix for
slice()andnew Blob([file]).[review] gate passed · iteration 8 · 3 files touched
fails on main (without fix)
passes on PR (with fix)
diff hotspot
gate history · 1 passed · 0 rejected · iteration 8
evidence per changed file