Skip to content

Add regression test for Bun.file().writer() with invalid path/fd in options - #30246

Open
robobun wants to merge 1 commit into
mainfrom
farm/537650de/blob-writer-invalid-options
Open

Add regression test for Bun.file().writer() with invalid path/fd in options#30246
robobun wants to merge 1 commit into
mainfrom
farm/537650de/blob-writer-invalid-options

Conversation

@robobun

@robobun robobun commented May 4, 2026

Copy link
Copy Markdown
Collaborator

What

Adds a regression test for Bun.file(path).writer(options) when the options object has a non-string path or non-integer fd property.

const f = Bun.file("/tmp/out.txt");
f.fd = Int16Array;
f.writer(f); // previously panicked in safe builds

Why

In the Zig implementation, Start.fromJSWithTag(..., .FileSink) returned a .err variant for these inputs, and Blob.getWriter accessed .FileSink unconditionally, panicking with access of union field 'FileSink' while field 'err' is active.

The Rust port (Blob.rs) already checks if let streams::Start::FileSink(...) before accessing the variant and falls back to the Blob's own path otherwise, so the crash no longer reproduces on main. This PR adds test coverage to guard against regression.

Fuzzer fingerprint: defc7abf146bf165

@github-actions github-actions Bot added the claude label May 4, 2026
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Crash in Bun runtime: Panic when accessing union field 'fd' while 'path' field is active #16053 - Same class of bug: panic from accessing a tagged union field without checking the active variant in Blob.zig's file writer code path

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #16053

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 44767e10-699c-4c30-94f1-ba5facfcde7d

📥 Commits

Reviewing files that changed from the base of the PR and between 01aa7cd and b7aff16.

📒 Files selected for processing (1)
  • test/js/bun/util/filesink.test.ts

Walkthrough

Fixes a potential crash in Blob.getWriter where stream_start was accessed as .FileSink without first verifying its variant type. Adds a conditional check and a test validating that invalid path/fd options are safely ignored.

Changes

Writer Options Safety

Layer / File(s) Summary
Core Implementation
src/runtime/webcore/Blob.zig
getWriter now conditionally checks if (stream_start == .FileSink) before updating the field; otherwise it reassigns to a new .FileSink struct with the correct input_path.
Test / Validation
test/js/bun/util/filesink.test.ts
New test case verifies that Bun.file().writer() safely ignores invalid fd (non-integer) and path (non-string) values instead of crashing, with verification that writes succeed.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and specifically describes the main change: a regression test for Bun.file().writer() handling of invalid path/fd options.
Description check ✅ Passed The description fully addresses both template sections with clear explanations of what the PR does and why it was needed, including context about the underlying bug fix.

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


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

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/runtime/webcore/Blob.zig`:
- Around line 2977-2982: The parsed stream_start from
jsc.WebCore.streams.Start.fromJSWithTag may own a .FileSink.input_path that must
be freed before you overwrite it; update the branch handling around stream_start
and .FileSink so you either deinit the existing stream_start.FileSink.input_path
before assigning input_path, or construct a new .FileSink object for the
fallback and assign that to stream_start without mutating the originally parsed
value. Ensure you reference stream_start and .FileSink and properly release any
owned path (deinit/free the previous input_path) before replacing to avoid
leaking the owned allocation.

In `@test/js/bun/util/filesink.test.ts`:
- Around line 272-301: Replace the use of tmpdirSync() in the test
"Bun.file().writer() ignores invalid path/fd in options instead of crashing"
(the local variable dir) with the harness-provided tempDir helper so the test
directory is disposable and auto-cleaned; change the dir assignment to call
tempDir() and add/import tempDir from the test harness, keeping the rest of the
test (join(dir, "a.txt") etc.) unchanged.
🪄 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: e8ae4241-c3e2-4b9b-a04a-f6a99dfefc76

📥 Commits

Reviewing files that changed from the base of the PR and between 0a7bed5 and 64cedb4.

📒 Files selected for processing (2)
  • src/runtime/webcore/Blob.zig
  • test/js/bun/util/filesink.test.ts

Comment thread src/runtime/webcore/Blob.zig Outdated
Comment thread test/js/bun/util/filesink.test.ts
@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. Fix crash in Bun.file().writer() with non-string path option #30232 - Fixes the same crash in Bun.file().writer() with non-string path option in Blob.getWriter
  2. Fix crash in Blob.writer() when options contain invalid fd or path #28388 - Fixes the same crash in Blob.writer() when options contain invalid fd or path
  3. Fix crash in Blob.writer() when options parsing returns an error #28736 - Fixes the same crash in Blob.writer() when options parsing returns an error
  4. Handle Start::Err variant in Blob.get_writer and FileSink.start #28497 - Handles the same error variant from fromJSWithTag in Blob.getWriter
  5. Fix crash in Blob.getWriter when options parsing returns an error #28700 - Fixes the same crash in Blob.getWriter when options parsing returns an error

🤖 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 union-tag check to fix a fuzzer-found panic; the inline notes are a pre-existing leak and a nit on garbage input, neither blocking.

Extended reasoning...

Overview

This PR changes 4 lines in src/runtime/webcore/Blob.zig (getWriter) and adds a regression test in test/js/bun/util/filesink.test.ts. Previously, after calling Start.fromJSWithTag(..., .FileSink), the code unconditionally accessed stream_start.FileSink.input_path. But fromJSWithTag returns a .err variant when options.path is not a string or options.fd is not an integer (streams.zig:127-150), causing a safe-build panic on inactive-union-field access. The fix checks the tag and falls back to a fresh .FileSink with the Blob's own path when the result isn't .FileSink. Three test cases cover the fuzzer repro (file.fd = Int16Array; file.writer(file)), explicit non-integer fd, and non-string path.

Security risks

None. This is a defensive tag check on a tagged union in a local-filesystem write path. No auth, crypto, parsing of untrusted network data, or permission logic is touched. The change strictly narrows behavior from a panic to the same write the Blob would have done with no options.

Level of scrutiny

Low. The diff is tiny, mechanical, and obviously correct: it replaces an unchecked union field access with a tag check + fallback to the exact same default that's already initialized at line 2970-2974. The fallback semantics (ignore irrelevant path/fd in options, since the Blob already carries its target) match the PR description and existing intent — the old code already overwrote input_path unconditionally.

Other factors

The bug-hunting system surfaced two findings, both posted inline: (1) a pre-existing leak where a valid options.path slice from fromJSWithTag is overwritten without .deinit() — this predates the PR and is unchanged by it; (2) a nit that highWaterMark is dropped in the .err fallback — only reachable with contrived garbage input that previously panicked, so any non-crashing behavior is strictly an improvement. Neither blocks this panic fix. The PR has clear test coverage that exercises the exact crash and verifies writes succeed afterward.

Comment thread src/runtime/webcore/Blob.zig Outdated
Comment thread src/runtime/webcore/Blob.zig Outdated

@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 — the leak fix and tempDir switch in 91379ca address the earlier feedback; the remaining highWaterMark note is a non-blocking nit.

Extended reasoning...

Overview

This PR fixes a fuzzer-found panic in Blob.getWriter (src/runtime/webcore/Blob.zig:2976-2983) where stream_start.FileSink was accessed without checking that fromJSWithTag actually returned the .FileSink variant rather than .err. The fix guards the union access, adds a .deinit() on the parsed input_path before overwriting it (closing a small pre-existing leak), and falls back to a fresh .FileSink when the parse failed. A regression test in test/js/bun/util/filesink.test.ts exercises four shapes of bad/overridden path/fd options.

Security risks

None. This only affects how invalid option types are handled when constructing a file writer for a Blob that already has its own path/fd; no new inputs reach the filesystem layer that didn't before.

Level of scrutiny

Low. The Zig change is 5 lines, mechanical (tag check + deinit + fallback struct), and strictly defensive — the previous behavior was a safe-build panic on garbage input. The test follows existing patterns in the same file. I verified that join(dir, ...) works with the tempDir return value because Bun's validateString (src/jsc/JSType.zig isStringLike) accepts DerivedStringObject, so the DisposableString passes.

Other factors

Commit 91379ca addressed both pieces of prior feedback: the parsed input_path is now freed before being overridden, and the test uses tempDir instead of tmpdirSync. My one outstanding inline note — that highWaterMark is dropped when the fallback branch is taken — only applies to inputs that previously crashed and is purely a buffer-sizing hint, so it does not block.

@robobun

robobun commented May 4, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 6:05 AM PT - Aug 14th, 2026

@robobun, your commit b7aff16 has some failures in Build #95621 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 30246

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

bun-30246 --bun

@robobun

robobun commented May 4, 2026

Copy link
Copy Markdown
Collaborator Author

CI status: builds #56882 and #56887 (before/after retrigger) both fail only on darwin-14-x64-test-bun, and the failing test is test/js/node/async_hooks/AsyncLocalStorage-tracking.test.ts > fs-watch — the spawned async-context-fs-watch.js segfaults at address 0xC in Bun's macOS FSEvents/fs.watch path. This PR is test-only (test/js/bun/util/filesink.test.ts, which passes 43/43 on that same shard), so the crash is unrelated and pre-existing on macOS x64 (see also #30758). Everything else is green.

Comment thread src/runtime/webcore/Blob.zig Outdated
@robobun robobun changed the title Fix panic in Bun.file().writer() with invalid path/fd in options Add regression test for Bun.file().writer() with invalid path/fd in options May 18, 2026

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

Thanks for dropping the Blob.zig hunk — now this is a clean test-only regression guard for the Rust impl. LGTM.

Extended reasoning...

Overview

After commit 0d24439 dropped the dead-code Blob.zig edit (per my prior review), this PR now touches only test/js/bun/util/filesink.test.ts: it imports tempDir from harness and adds one regression test verifying that Bun.file(path).writer({...}) ignores invalid path/fd option types instead of panicking. The behavior under test already ships in src/runtime/webcore/Blob.rs:1935-1943, so this is purely a regression guard with no runtime/source changes.

Security risks

None. Test-only change; no auth, crypto, permissions, or input-handling code is modified.

Level of scrutiny

Low. The diff adds ~40 lines of test code that write to a disposable temp directory and assert file contents. CI build #55665 ran on commit 2df71ae (which already contained this exact test, on top of the Rust rewrite) and the only failure was an unrelated v8-heap-snapshot.test.ts SIGKILL on one Linux shard. I verified that Bun's path.join accepts DerivedStringObject (JSValue.rs:281 → JSType.rs:794-798 includes DerivedStringObject in is_string_like), so join(dir, ...) works with the DisposableString returned by tempDir even though Node.js would reject it — and CI confirms the test passes.

Other factors

All prior review feedback has been addressed: CodeRabbit's input_path.deinit() and tempDir suggestions were applied in 2df71ae (both threads resolved), and my dead-code observation was addressed in 0d24439. My remaining unresolved nit about highWaterMark in the .err fallback targeted Blob.zig which is no longer compiled, so it's moot. The bug-hunting system found no issues in the current revision.

@robobun
robobun force-pushed the farm/537650de/blob-writer-invalid-options branch from 0d24439 to 73b5d85 Compare May 22, 2026 16:09

@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 — thanks for dropping the Blob.zig hunk; test-only regression guard now.

Extended reasoning...

Overview

This PR now touches a single file, test/js/bun/util/filesink.test.ts, adding one regression test ("Bun.file().writer() ignores invalid path/fd in options instead of crashing") and importing tempDir from the harness. No production code is modified. The test covers four variants of garbage path/fd values in the options object passed to Bun.file(p).writer(opts) and asserts the write still lands at the Blob's own path.

What changed since my last review

On 2026-05-18 I flagged that the src/runtime/webcore/Blob.zig change was dead code (the shipped implementation is Blob.rs, which already handles the non-FileSink variant). The author dropped that hunk and retitled/redescribed the PR as a test-only regression guard, which is exactly what I suggested. The earlier CodeRabbit nits (use tempDir instead of tmpdirSync; deinit the parsed input_path) are also resolved — the former is reflected in the diff and the latter is moot now that the Zig change is gone. All prior inline comments on the PR are marked resolved.

Security risks

None. This is a test-file-only addition that writes to a disposable temp directory created via the harness tempDir helper. It does not touch auth, crypto, network, or any production code path.

Level of scrutiny

Low. Test-only, ~40 lines, follows existing patterns in the same file (e.g. the neighboring start() without path/fd test), uses the recommended using dir = tempDir(...) idiom for cleanup, and join(dir, ...) works because tempDir returns a string & Disposable. The as any casts are intentional to feed type-invalid inputs that exercise the runtime's defensive path.

Other factors

No CODEOWNERS entry covers this file. CI on an earlier revision (0d24439) was green with only an unrelated agent-queue expiry; the latest build (#56882 on 73b5d85) is in progress. The behavior under test already ships in Blob.rs, so the test is purely a guard against future regression and carries no functional risk.

@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 — thanks for dropping the Blob.zig hunk; test-only regression guard now.

Extended reasoning...

Overview

This PR now touches a single file, test/js/bun/util/filesink.test.ts, adding one regression test (~39 lines) that exercises Bun.file().writer(options) with invalid fd/path option types. No production code is modified — the underlying fix already shipped in Blob.rs as part of the Rust rewrite, and this test guards against regression.

Prior feedback addressed

On 2026-05-18 I flagged that the src/runtime/webcore/Blob.zig edit was dead code (Zig sources are non-compiled porting references post-Rust-rewrite) and recommended keeping only the test. That hunk has since been removed; the PR is now purely additive test coverage. The CodeRabbit nit about tempDir vs tmpdirSync was also applied, and the join(dir, ...) pattern with a DisposableString matches existing usage (e.g. test/js/bun/shell/leak.test.ts). All prior inline comments are resolved.

Security risks

None. Test-only change writing single-byte files into an auto-cleaned temp directory.

Level of scrutiny

Low. This is an additive regression test in the test suite with no runtime impact. The four sub-cases are straightforward write-then-read assertions following the same patterns as adjacent tests in the file.

Other factors

CI build #56882 shows one failure in AsyncLocalStorage-tracking.test.ts (segfault on macOS 14 x64), which is unrelated to this change. The earlier build on this branch (#55674) was green. The bug-hunting system found no issues in the current diff.

@robobun

robobun commented May 22, 2026

Copy link
Copy Markdown
Collaborator Author

CI status on b7aff16 (build #95621): all four failures are flagged flaky by the CI tooling and are unrelated to this test-only change (inspect-error-leak.test.js and malformed-integrity-base64.test.ts pass alone; test-fs-read-stream-pos.js and test-watch-mode-kill-signal-default.mjs pass on retry). filesink.test.ts, the only file this PR touches, passes on every lane. Re-roll was already used earlier; not retriggering again.

@robobun
robobun force-pushed the farm/537650de/blob-writer-invalid-options branch from a376ddb to 5c490f9 Compare June 18, 2026 12:44

@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 — thanks for dropping the dead Blob.zig hunk and switching to tempDir; this is now a clean test-only regression guard.

Extended reasoning...

Overview

The PR now touches a single file, test/js/bun/util/filesink.test.ts, adding one ~38-line regression test that exercises Bun.file(path).writer(options) with four malformed/irrelevant path/fd option shapes (non-integer fd via self-reference, explicit string fd, numeric path, and a string path that should be overridden by the Blob's own path). Each case writes a byte and asserts the file content. The earlier src/runtime/webcore/Blob.zig change has been removed per prior feedback — the Rust port (Blob.rs) already handles the non-FileSink variant, so this PR is purely a regression guard for behavior that already ships.

Security risks

None. This is a test-only change that runs in the test harness's temp directory, performs no network/auth/crypto operations, and does not alter any shipped runtime code.

Level of scrutiny

Low. Test-only additions to an existing test file, following the established patterns in that file (using dir = tempDir(...), Bun.file(...).writer(), await writer.end()). The tempDir helper returns a disposable String subclass, so join(dir, ...) and the using cleanup both work as intended. The worst-case failure mode is a flaky/failing test, not a runtime regression.

Other factors

  • All prior review feedback is addressed and marked resolved: the dead Blob.zig edit was dropped, the CodeRabbit tempDir nit was applied, and the title/description were updated to reflect the test-only scope.
  • CI (robobun) reports the only failure is the pre-existing, unrelated macOS x64 AsyncLocalStorage-tracking.test.ts > fs-watch segfault; filesink.test.ts itself passes 43/43 on that shard.
  • The bug-hunting system found no issues in the current diff.
  • I have not previously approved this PR; my earlier comments were on the since-removed Blob.zig hunk.

…ptions

The Zig implementation used to panic accessing the FileSink union field
when Start.fromJSWithTag returned the err variant for a non-string path
or non-integer fd in the options object. The Rust port already checks
the variant before accessing it, so this adds coverage to guard against
regression.
@robobun
robobun force-pushed the farm/537650de/blob-writer-invalid-options branch from 5c490f9 to b7aff16 Compare August 14, 2026 06:48

@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 — test-only regression guard now that the Blob.zig hunk is dropped.

What was reviewed:

  • Confirmed Blob.rs:1913-1920 still has the if let streams::Start::FileSink fallback the test guards, and that a valid options.path is overridden by the Blob's own path (4th sub-case).
  • join(dir, ...) on a tempDir DisposableString matches existing usage (e.g. patch.test.ts), so no arg-type issue.
  • Prior nits (tempDir over tmpdirSync, dead Blob.zig edit) are addressed; all earlier inline comments resolved.
Extended reasoning...

Overview

The PR now touches a single file, test/js/bun/util/filesink.test.ts, adding one it() block with four sub-cases exercising Bun.file(path).writer(options) when options carries a non-integer fd, a non-string path, or a valid string path that should be overridden by the Blob's own path. Earlier revisions also edited src/runtime/webcore/Blob.zig, but that hunk was dropped after my 2026-05-18 review flagged it as dead reference code post-Rust-rewrite; the diff is now purely additive test coverage.

Security risks

None. The test writes single-byte files into a per-test tempDir that is disposed via using, reads them back, and asserts exact contents. No network, no external processes, no security-sensitive code paths.

Level of scrutiny

Low. This is a small test-only addition guarding behavior that already ships in src/runtime/webcore/Blob.rs (verified at lines 1913-1920: the if let streams::Start::FileSink(ref mut opts) check with an else fallback that reconstructs FileSink from the Blob's own input_path). The test follows harness conventions: using dir = tempDir(...), exact .toBe(...) assertions on file contents rather than bare not.toThrow(), and no sleeps or hardcoded ports. I checked that join(dir, ...) on the DisposableString return of tempDir is an established pattern in the suite (test/js/bun/patch/patch.test.ts uses it extensively), so Bun's path.join accepts the boxed String.

Other factors

All four of my prior inline comments and both CodeRabbit comments are marked resolved and were on lines no longer in the diff. The two robobun CI comments report the test file passing on all shards with unrelated flakes elsewhere; binary size delta is 0 KB as expected for test-only. No human reviewers have outstanding comments. The PR description accurately reflects the current scope (regression guard for the Rust impl, no source change).

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.

1 participant