Skip to content

fetch: reject a non-string unix option instead of dialing the URL host over TCP - #41786

Open
robobun wants to merge 1 commit into
mainfrom
robobun/85f68d25/fetch-unix-non-string
Open

robobun wants to merge 1 commit into
mainfrom
robobun/85f68d25/fetch-unix-non-string

Conversation

@robobun

@robobun robobun commented Sep 7, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • fetch(url, { unix }) ignores unix when it is not a JS string. A Buffer, Uint8Array, URL, number, true, Symbol, or an object with toString all send the request to the URL's host over TCP. There is no error and no coercion.
  • The cause is the unix extraction in fetch_impl (src/runtime/webcore/fetch.rs:690). It takes the option only when is_string() && length > 0 and falls through for everything else.

Fix

  • A truthy non-string unix now rejects with TypeError (ERR_INVALID_ARG_TYPE): fetch: 'unix' must be a string. Received an instance of Buffer. No request is sent.
  • Falsy values ("", null, undefined, false, 0) still mean "no unix socket". This matches Bun.connect and Bun.listen, whose unix member is String.nullable.loose: falsy is absent, a truthy non-string throws SocketOptions.unix must be a string.
  • Correct because the option can no longer change the transport in silence. fetch_impl errors become rejected promises through reject_on_exception, so fetch() still never throws synchronously.
  • Verified: test/js/web/fetch/fetch.unix.test.ts (two new tests, stock bun fails the first). Also test/js/web/fetch/fetch-args.test.ts.

Background

  • unix is a Bun extension to fetch init. It dials a unix domain socket and sends the request for url over it. The docs (docs/runtime/networking/fetch.mdx) give its type as unix: string.
  • fetch_impl reads each Bun extension from fetch's init object and, when the first argument is a plain object, from that object. Both paths go through the same extraction block, so both get the new check.
  • Node has no unix option on fetch. Its closest analogue, net.connect({ path: Buffer }), throws ERR_INVALID_ARG_TYPE.
Notes

Repro on 1.4.3-canary (f42e98025), every non-string row answered by the TCP server:

import os from "node:os"; import path from "node:path";
const sock = path.join(os.tmpdir(), `r-${process.pid}.sock`);
const unixSrv = Bun.serve({ unix: sock, fetch: () => new Response("via UNIX") });
const tcpSrv = Bun.serve({ port: 0, hostname: "127.0.0.1", fetch: () => new Response("via TCP") });
const U = `http://127.0.0.1:${tcpSrv.port}/x`;
for (const [k, v] of Object.entries({ string: sock, buffer: Buffer.from(sock), url: new URL("file://" + sock), number: 42, toStringObj: { toString: () => sock }, empty: "", nul: null }))
  console.log(k.padEnd(12), await fetch(U, { unix: v }).then(r => r.text(), e => "REJECT " + e.message));
unixSrv.stop(true); tcpSrv.stop(true);

Before: string -> via UNIX, every other row -> via TCP. After: buffer, url, number, toStringObj reject with ERR_INVALID_ARG_TYPE. empty and nul still go to TCP.

Accepting Buffer/URL like node:fs path arguments was considered. Bun.connect, Bun.listen, and the docs are string-only, so this change keeps fetch consistent with them instead of adding a new accepted shape in one place.

No built-in JS module passes unix to native fetch (node:http uses raw sockets for socketPath), so nothing internal depends on the old fall-through.

Out of scope here: new Request(url, { unix }) does not keep the option, so fetch(request) goes over TCP. That is a separate design question (store Bun extensions on Request, or throw in the constructor).


no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/web/fetch/fetch.unix.test.ts

…t over TCP

fetch(url, { unix }) only used the option when it was a non-empty JS
string. A Buffer, URL, number, or object was skipped, so the request
went to the URL's host over TCP with no error. A truthy non-string now
rejects with TypeError ERR_INVALID_ARG_TYPE, like Bun.connect and
Bun.listen. Falsy values still mean no unix socket.
@robobun

robobun commented Sep 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status

Reproduced on 1.4.3-canary (f42e98025): with a TCP server on the URL's host and port, fetch(url, { unix: v }) answered via TCP for v = Buffer, Uint8Array, URL, { toString }, 42, true, and Symbol(). Only a JS string reached the unix socket.

Fail-before: USE_SYSTEM_BUN=1 bun test test/js/web/fetch/fetch.unix.test.ts -t "unix option" fails the new rejects a non-string unix option test (7 of 7 rows answered over TCP). With this branch: 17/17 in fetch.unix.test.ts and 61/61 in fetch-args.test.ts pass under the debug ASAN build.

CI (build 111927): the diff is green on every platform, including the new tests on Windows and macOS. The two red jobs do not touch it: one darwin aarch64 shard timed out downloading the build artifact before any test ran (fetch.unix.test.ts ran and passed on the other shard), and test-crypto-dh-leak.js on x64-asan also fails on main. Ready for review.

@coderabbitai

coderabbitai Bot commented Sep 7, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 7394f7b4-70b3-449c-9a94-062e5c54609c

📥 Commits

Reviewing files that changed from the base of the PR and between d316760 and 2460474.

📒 Files selected for processing (2)
  • src/runtime/webcore/fetch.rs
  • test/js/web/fetch/fetch.unix.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.


Walkthrough

fetch() now rejects truthy non-string unix options with TypeError ERR_INVALID_ARG_TYPE. Falsy values disable Unix-socket routing and use the URL’s TCP endpoint. Tests cover invalid types and falsy values.

Changes

Unix fetch option validation

Layer / File(s) Summary
Validate unix options and routing
src/runtime/webcore/fetch.rs, test/js/web/fetch/fetch.unix.test.ts
fetch() rejects truthy non-string values, preserves non-empty string paths, and treats falsy values as absent. Tests cover invalid types and TCP fallback behavior.

Suggested reviewers: alii, dylan-conway

Merge Risk: ⚪ Minimal · up to 24604

Fetch now rejects truthy non-string unix options rather than silently falling back to TCP, while falsy values retain TCP routing. No concrete merge-blocking risk remains.

🚥 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 summarizes the main change: rejecting non-string unix options instead of using the URL host over TCP.
Description check ✅ Passed The description explains the problem, the fix, expected behavior, scope, and verification. It does not use the exact template headings, but it provides the required information and is complete enough …

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

@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 — tightens a silent-misroute into a loud ERR_INVALID_ARG_TYPE, no change to previously-valid inputs.

What was reviewed:

  • The new branch in fetch.rs: to_boolean() is the non-throwing JS ToBoolean (safe on Symbol), determine_specific_type and to_bun_string propagate with ?, and the throw_invalid_arguments shape matches the neighboring protocol block.
  • Falsy handling: ""/null/undefined/false/0 all fall through to "no unix socket" as before, matching Bun.connect's String.nullable.loose; only truthy non-strings now reject.
  • Tests: added to the existing fetch.unix.test.ts, port: 0 + using cleanup, full type matrix, exact class/code/message asserted, and requests === 0 proves no TCP dial happened.
Extended reasoning...

Overview

This PR changes option validation for the Bun-specific unix extension to fetch(). In src/runtime/webcore/fetch.rs, the extract_unix_socket_path block previously accepted only is_string() && length > 0 and silently fell through for anything else, causing a request intended for a unix socket to be dialed over TCP to the URL's host. The new code keeps the string path unchanged, but any truthy non-string now returns Err(throw_invalid_arguments(...)), which reject_on_exception turns into a rejected promise. Falsy values remain "absent". Two tests in test/js/web/fetch/fetch.unix.test.ts cover the reject matrix (Buffer, Uint8Array, URL, {toString}, number, boolean, Symbol) with a zero-request assertion, and the falsy passthrough.

Security risks

The change is security-positive: it closes a silent transport misroute where a caller passing Buffer.from(path) would unknowingly send the request over TCP to the URL host. No new input is accepted, no new coercion runs on user values (the added calls are to_boolean() which never enters user JS, and determine_specific_type for the error message). No auth, crypto, or permission surface is touched.

Level of scrutiny

Low-to-moderate. The native diff is ~12 lines inside an existing labeled block, structurally identical to the protocol validation immediately below it (same throw_invalid_arguments(format_args!(...)) pattern). Exception discipline is correct: get/get_length/to_bun_string/determine_specific_type all carry ?, and to_boolean() is confirmed non-fallible in src/jsc/JSValue.rs. No CODEOWNERS entry covers these paths.

Other factors

Tests follow repo conventions closely: existing module test file, port: 0, using for server lifetime, single .toEqual on a results object, exact error class + .code + message asserted, negative contract via request counter, no sleeps or external network. The hunt exited on dry_streak with no findings and no ruled-out candidates. The PR timeline shows only the initial commit — no prior reviews or outstanding objections.

@robobun

robobun commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 9:41 PM PT - Sep 6th, 2026

❌ @robobun, your commit 2460474 has 2 failures in Build #111927 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 41786

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

bun-41786 --bun

This branch has not been deployed

No deployments
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