Skip to content

fetch: honor tls.ca/cert/key over unix sockets - #35885

Closed
robobun wants to merge 4 commits into
mainfrom
claude/43fa620f/fetch-unix-tls-ca-cert-key
Closed

robobun wants to merge 4 commits into
mainfrom
claude/43fa620f/fetch-unix-tls-ca-cert-key

Conversation

@robobun

@robobun robobun commented Jul 26, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

fetch(url, { unix, tls }) silently dropped tls.ca, tls.cert, and tls.key while still honoring tls.rejectUnauthorized, tls.servername, and tls.checkServerIdentity. The same tls object works correctly on a TCP fetch to the same server.

Net effect: HTTPS over a unix socket could not use a private CA and could not do mTLS. A ca for the correct root still failed with DEPTH_ZERO_SELF_SIGNED_CERT, and a client cert was never presented to a requestCert server. The only way to make it work was rejectUnauthorized: false.

Cause

HttpThread::connect took the unix-socket early return before the per-request custom SSL_CTX path ('custom_ctx) had a chance to run, so the unix connect always used the process-default HTTPS context. ca/cert/key are SSL_CTX-level options baked into that per-request context; rejectUnauthorized/servername/checkServerIdentity are read per request off HTTPClient regardless of which context dialed the socket, which is why those kept working.

Fix

When tls_props requires a custom context, dial the unix socket through that context (both cache-hit and cache-miss arms), mirroring the TCP path. The default-context unix dial now runs only when no custom context is needed.

Verification

# before (canary)
unix tls.ca      -> ERR DEPTH_ZERO_SELF_SIGNED_CERT
unix cert+key    -> ERR DEPTH_ZERO_SELF_SIGNED_CERT
tcp  tls.ca      -> 200 ok

# after
unix tls.ca      -> 200 ok
unix cert+key    -> 200 ok-mtls
tcp  tls.ca      -> 200 ok

New tests in test/js/web/fetch/fetch.unix.test.ts cover both the private-CA case and the mTLS case. Both fail on canary with DEPTH_ZERO_SELF_SIGNED_CERT and pass with this change. fetch.tls.test.ts, fetch-tls-cert.test.ts, and ssl-ctx-cache.test.ts remain green.


no test proof · iteration 1 · 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

HttpThread::connect returned early for unix sockets before the custom
SSL_CTX path ran, so ca/cert/key (which are baked into an SSL_CTX) were
silently dropped while per-request options like rejectUnauthorized and
servername continued to work. Dial unix through the custom SSL_CTX when
one is needed, same as TCP.
@coderabbitai

coderabbitai Bot commented Jul 26, 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: Pro

Run ID: a9b509f4-d337-436a-ad69-cb88c5591ba6

📥 Commits

Reviewing files that changed from the base of the PR and between 4d16b0b and d18e65d.

📒 Files selected for processing (1)
  • test/js/web/fetch/fetch.unix.test.ts

Walkthrough

Changes

Unix Socket TLS

Layer / File(s) Summary
Route custom TLS contexts over Unix sockets
src/http/HTTPThread.rs
Unix-socket routing is deferred through custom TLS context selection, and selected custom contexts use connect_socket for cache hits and misses.
Validate TLS fetches over Unix sockets
test/js/web/fetch/fetch.unix.test.ts
Added CA verification and mutual TLS tests using HTTPS servers hosted on Unix sockets.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main fix: honoring TLS CA/cert/key over Unix sockets.
Description check ✅ Passed The description covers the required purpose and verification details, with clear cause, fix, and test evidence.
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.

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

@robobun

robobun commented Jul 26, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 4:24 AM PT - Jul 26th, 2026

❌ @robobun, your commit d18e65d has 1 failures in Build #82214 (All Failures):

  • 📦 Binary size — 12 over 0.50 MB
  • targetthis build canary: main #79916
    sizeΔ
    ❌ bun-darwin-aarch6458.13 MB57.58 MB+564.9 KB
    ❌ bun-darwin-x6463.48 MB62.95 MB+544.5 KB
    ❌ bun-linux-aarch6470.98 MB70.42 MB+576.0 KB
    ❌ bun-linux-x6472.47 MB71.95 MB+528.0 KB
    ❌ bun-linux-aarch64-musl64.88 MB64.32 MB+576.0 KB
    ❌ bun-linux-x64-musl66.98 MB66.45 MB+544.0 KB
    ❌ bun-linux-aarch64-android78.47 MB77.97 MB+512.0 KB
    ❌ bun-linux-x64-android80.62 MB80.10 MB+529.2 KB
    ❌ bun-freebsd-x6483.07 MB82.56 MB+528.0 KB
    ❌ bun-freebsd-aarch6484.84 MB84.31 MB+544.0 KB
    ❌ bun-windows-x6480.26 MB79.70 MB+571.0 KB
    ❌ bun-windows-aarch6470.86 MB70.34 MB+533.0 KB

    Add [skip size check] to the commit message if this increase is intentional.


🧪   To try this PR locally:

bunx bun-pr 35885

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

bun-35885 --bun

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/http/HTTPThread.rs (1)

501-517: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Cache-hit proxy dispatch needs the same protocol guard as the other paths.
The client.http_proxy branch here should reject unsupported proxy schemes the same way the cache-miss and fallback paths do; otherwise cached SSLConfigs can take a different connect path for the same request. Consider factoring the shared dispatch into one helper to keep the branches aligned.

🤖 Prompt for 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.

In `@src/http/HTTPThread.rs` around lines 501 - 517, The cache-hit branch in the
custom SSL context flow must validate proxy schemes before dispatching through
client.http_proxy, matching the cache-miss and fallback paths. Update the
dispatch around custom_ssl_context_map and ctx.connect to reuse the existing
protocol guard or shared helper, while preserving Unix-socket and direct-connect
behavior.
🤖 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 `@test/js/web/fetch/fetch.unix.test.ts`:
- Line 249: Replace the shared sockPath helper’s tmpdirSync usage with tempDir
from harness, declaring the disposable directly with using inside each affected
it() body and retaining it for the socket’s lifetime. Update all affected test
cases and remove the helper pattern that discards the tempDir resource.
- Around line 248-287: Wrap the “tls over unix socket” describe block containing
the `sockPath`, `validTls`, and mTLS tests in the existing Windows platform skip
mechanism, so neither test runs on Windows while behavior on supported platforms
remains unchanged.

---

Outside diff comments:
In `@src/http/HTTPThread.rs`:
- Around line 501-517: The cache-hit branch in the custom SSL context flow must
validate proxy schemes before dispatching through client.http_proxy, matching
the cache-miss and fallback paths. Update the dispatch around
custom_ssl_context_map and ctx.connect to reuse the existing protocol guard or
shared helper, while preserving Unix-socket and direct-connect behavior.
🪄 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: 56fe5e3c-01b3-42e2-90ab-f1b5f69cda2d

📥 Commits

Reviewing files that changed from the base of the PR and between 44f6469 and 4d16b0b.

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

Comment thread test/js/web/fetch/fetch.unix.test.ts
Comment thread test/js/web/fetch/fetch.unix.test.ts Outdated
Comment thread test/js/web/fetch/fetch.unix.test.ts Outdated
@robobun

robobun commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Re the out-of-diff note on HTTPThread.rs:501-517 (cache-hit proxy branch lacking the has_http_like_protocol() guard): that asymmetry predates this PR; I only added the unix_path arm and left the existing proxy dispatch untouched. Happy to align both branches in a follow-up, but keeping this change scoped to the unix-socket TLS fix.

@robobun

robobun commented Jul 26, 2026 •

Copy link
Copy Markdown
Collaborator Author

CI on d18e65d: the new fetch.unix.test.ts cases pass on every lane including Windows. The only failures are unrelated to this diff:

  • binary-size is comparing against a stale canary baseline (main #79916, ~2000 builds behind current main) and reports the same +528/+576 KB deltas on other open PRs built today (e.g. builds 82108, 82106).
  • inspect.test.ts, watch-many-dirs.test.ts, webview-chrome.test.ts, jsc-stress.test.ts are known flakes already flagged on main.

Ready for review.

Comment thread test/js/web/fetch/fetch.unix.test.ts

@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 — all prior feedback (redundant server.stop alongside using, tempDir vs tmpdirSync, and covering the SSL_CTX cache-hit arm) has been addressed and the threads are resolved.

What was reviewed:

  • Traced every path through the reordered HttpThread::connect: non-SSL+unix, SSL+unix without custom tls, SSL+unix with custom tls (cache hit and miss), and SSL+TCP/proxy — all preserve prior behavior except the intended fix.
  • Confirmed init_with_client_config and the rest of the 'custom_ctx block do not touch client.unix_socket_path, so the updated RawSlice invariant comment holds.
  • unix-path precedence over http_proxy is preserved in both new arms, matching the default-context path.
Extended reasoning...

Overview

The PR reorders HttpThread::connect in src/http/HTTPThread.rs so the per-request custom SSL_CTX block ('custom_ctx) runs before the unix-socket dial, and adds a !unix_path.is_empty() arm to both the cache-hit and cache-miss branches so the unix socket is dialed through the custom context. The default-context unix dial moved from before 'custom_ctx to after it. Two new tests in test/js/web/fetch/fetch.unix.test.ts cover private-CA verification and mTLS over a unix socket, with the CA test looping twice to exercise the cache-hit arm.

Security risks

None introduced. The change is in the direction of enabling proper certificate verification and client-cert presentation over unix sockets — before this fix, users had to set rejectUnauthorized: false to make HTTPS-over-unix work at all with a private CA. rejectUnauthorized, servername, and checkServerIdentity are read off HTTPClient per request and are unaffected by which SSL_CTX dials the socket; only ca/cert/key (SSL_CTX-level) change behavior, and only on the unix+custom-tls path that previously ignored them.

Level of scrutiny

Moderate. HTTPThread::connect is on the hot path for every HTTP client request, but the change is a pure control-flow reorder with no new unsafe blocks, no new allocations, and no lifetime changes. I verified the RawSlice invariant (backing client.unix_socket_path is not mutated inside 'custom_ctx — init_with_client_config only reads tls_props), and traced each of the six connect paths (SSL/non-SSL × unix/proxy/TCP × custom-ctx/default) to confirm behavior is preserved everywhere except the targeted fix. unix-path precedence over http_proxy is retained in both new arms.

Other factors

All three prior review threads (two from me, one from CodeRabbit) are resolved: the redundant server.stop(true) alongside using is gone, tempDir replaced tmpdirSync in the new tests, and the CA test now loops twice — the author verified the second iteration is load-bearing (fails with ConnectionRefused when the cache-hit connect_socket arm is reverted). The author reported CI green on all lanes including Windows for 0b82aee with only known-flake failures unrelated to this diff; d18e65d only adds the second fetch iteration. No CODEOWNERS entry covers src/http. The pre-existing asymmetry in the cache-hit proxy branch (missing has_http_like_protocol() guard) predates this PR and the author correctly deferred it.

Jarred-Sumner pushed a commit that referenced this pull request Aug 26, 2026
…uest header (#35886)

### Problem
- `Host: whatever` on a request to `https://a.example/` fails with
`ERR_TLS_CERT_ALTNAME_INVALID` because the server answers for the
header's name (#26579).
- Cause: `get_tls_hostname()` (`src/http/lib.rs`) preferred the Host
header over `url.hostname` for SNI and the certificate hostname check.

### Fix
- `get_tls_hostname()` resolves to `tls.servername` when set, otherwise
`url.hostname`. The Host header still goes on the wire and no longer
touches TLS.
- Remove the plumbing that carried the header to TLS, including its term
in the connection pool keys (list in Notes). The proxy-tunnel inner SNI
uses `get_tls_hostname()` too.
- Correct because the TLS identity is the authority the client dialled
(RFC 6125 / RFC 9525). Node's `fetch` (undici) and curl do the same.
`tls.servername` stays the opt-in for "dial an IP, verify a name".
- Verified: `test/js/web/fetch/fetch.tls.test.ts` (three cases) and
`test/js/bun/http/proxy.test.ts` (tunnel case) fail on stock bun and
pass here. Neighbouring TLS, proxy, keep-alive and h2 suites pass
(Notes).

### Breaking change
- `fetch("https://10.0.0.5/", { headers: { Host: "internal.example" }
})` against a certificate for `internal.example` used to pass
verification and now fails with `ERR_TLS_CERT_ALTNAME_INVALID`.
Migration: add `tls: { servername: "internal.example" }`. Node's `fetch`
already fails this case, so the migrated code is also portable.

### Background
- SNI is the hostname in the TLS ClientHello. A server with several
certificates picks one by it.
- Certificate verification compares the SAN entries to the name the
client meant to reach. If SNI and that name differ, an SNI-routing
server returns a certificate that cannot pass.
- The keep-alive pool, h2 session registry and TLS session cache share
one key. Requests with different Host headers now share one connection,
as for plain HTTP.
- #35889 changed only the verification target and kept the Host header
as SNI. Against an SNI-routing server that fails every request.

<details><summary>Notes</summary>

Node's `fetch` deletes a caller-supplied Host header before dispatch
(undici `lib/web/fetch/index.js`,
`httpRequest.headersList.delete('host')`, present since undici 5). Bun
keeps sending the header, which users rely on (#20173); only its effect
on TLS changes here. Node's `https.request` is different: its agent
derives `servername` from the Host header in JS (`calculateServerName`).
Bun's `node:http` client is a socket-based port of that code and does
not go through this HTTP client, so it is unaffected.

Removed plumbing, in full: `HTTPClient.hostname`,
`FetchOptions.hostname`, `FetchTasklet.hostname`,
`async_http::Options.hostname`, the `init_sync` hostname parameter (15
call sites), `InternalStateFlags.clear_hostname_on_redirect` and its
set/consume sites in `do_redirect`, `do_redirect_multiplexed` and
`handle_response_metadata`, the Host branch of `proxy_auth_hash()`, the
direct-TLS `proxy_auth_hash()` terms in `HTTPContext::connect`,
`release_socket` and `session_cache::install` (now CONNECT tunnels only,
which is what the hash covers), `h2::ClientSession.host_header_hash`,
`h2::PendingConnect.host_header_hash`, `strip_port_from_host()`.

Suites run with the fix: `fetch.tls`, `fetch-keepalive`,
`fetch-http2-adversarial`, `fetch.tls.wildcard`, `fetch.unix`,
`fetch-proxy-tls-intern-race`, `fetch-tls-abortsignal-timeout`,
`regression/issue/27890`, `bun-serve-ssl`, `node/tls/ssl-ctx-cache`, all
of `bun/http/proxy.test.ts`. `fetch.test.ts` has 41 failures in my
container that reproduce on stock bun too (`localhost` binds to `::1`
there); none touch this diff.

Branch history: the first version of this PR (July) conflicted with main
after the h2 `PeerVerification` and TLS session cache changes. This is
the same change re-applied on current main, plus the tests from #35889
that fit this design (the URL-host verify cases and the
`checkServerIdentity` hostname check) and a CONNECT tunnel case.
#35889's unix-socket `SSL_CTX` fix is a separate bug and lives in
#35885.

</details>

Fixes #26579
@robobun

robobun commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Closing as part of a cleanup of stale pull requests. This PR has had no new commits since 2026-07-26, it conflicts with main, and its last CI run failed. This is not a judgment on the fix itself. If the problem still reproduces on a current build, reopen this PR after a rebase or open a new one against main.

@robobun robobun closed this Sep 13, 2026
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