node:https: invoke SNICallback on the server - #33536
Conversation
https.Server forwarded only a fixed subset of TLS options into Bun.serve's tls config, so a SNICallback was dropped: every client, whatever servername it sent, was served the default certificate and the callback never ran. Wire a per-handshake certificate selector through Bun.serve. uSockets already supported the dynamic-resolver shape for node:tls/Bun.listen (us_listen_socket_on_server_name + us_socket_sni_resolve); this exposes the same contract to the uWS App that Bun.serve uses, forwards the node:https SNICallback into it, and adds a suspension registry so an asynchronous callback parks the handshake instead of falling through to the default context. The callback can also refuse the connection, which surfaces as tlsClientError, matching Node and the existing node:tls behavior.
|
Reproduced on stock 1.4.0: a CI: all review feedback addressed and clippy is green. Latest build 69599 on b18ec7c has 281/287 lanes passed; the only hard failure is |
|
Updated 2:16 AM PT - Jul 7th, 2026
❌ @robobun, your commit b18ec7c has 2 failures in
🧪 To try this PR locally: bunx bun-pr 33536That installs a local version of the PR into your bun-33536 --bun |
|
Warning Review limit reached
Next review available in: 39 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 (2)
WalkthroughThis PR adds asynchronous SNICallback support spanning uSockets/OpenSSL handshake suspension bookkeeping, uWS App server-name dispatch, a new Rust ServerSNI module with FFI resume bridging, ServerConfig on_server_name plumbing, and node:https/_http_server.ts SNICallback integration, plus new tests. ChangesAsync SNICallback support
Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
Found 5 issues this PR may fix:
🤖 Generated with Claude Code |
|
Thanks. Added |
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 `@packages/bun-usockets/src/crypto/openssl.c`:
- Around line 216-227: The helper us_ssl_sni_pending_get currently allocates a
new us_ssl_sni_pending_t and assumes SSL_set_ex_data succeeds, but that call can
fail and leave the allocation leaked. Update the SSL_get_ex_data /
SSL_set_ex_data path in us_ssl_sni_pending_get so that if SSL_set_ex_data
returns failure you free the newly allocated pending state and return NULL
instead of storing a detached object; keep the existing behavior for the
us_ssl_sni_pending_idx guard and the successful retrieval path.
In `@test/js/node/http/node-https-sni.test.ts`:
- Around line 37-211: These independent https.Server/SNICallback tests should
run concurrently instead of sequentially. Update the individual it() cases in
node-https-sni.test.ts to use test.concurrent (or wrap the block in
describe.concurrent) since each case creates its own server on port 0 and does
not share mutable state; keep the same assertions and cleanup logic in each test
while making the concurrency explicit.
🪄 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: dcb35c8a-8517-4a07-b419-b1dc9ce8a953
📒 Files selected for processing (15)
packages/bun-usockets/src/crypto/openssl.cpackages/bun-usockets/src/libusockets.hpackages/bun-uws/src/App.hpackages/bun-uws/src/HttpContextData.hsrc/js/internal/http.tssrc/js/node/_http_server.tssrc/jsc/bindings/NodeHTTP.cppsrc/runtime/server/ServerConfig.rssrc/runtime/server/ServerSNI.rssrc/runtime/server/mod.rssrc/uws_sys/App.rssrc/uws_sys/_libusockets.hsrc/uws_sys/libuwsockets.cppsrc/uws_sys/us_socket_t.rstest/js/node/http/node-https-sni.test.ts
What
A
node:httpsserver never invokesSNICallback. Every client, whateverservernameit sends, is served the server's default certificate, so multi-domain ("one server, N domains") HTTPS servers present the wrong certificate for every non-default name and real browsers hard-fail with a name mismatch.Repro
Cause
https.Serverishttp.Server, which buildsBun.serve({ tls }).src/js/node/_http_server.tsforwarded only a fixed subset of TLS options (serverName,key,cert,ca,passphrase,secureOptions,requestCert,rejectUnauthorized) into the config, soSNICallbackwas dropped on the floor.Bun.servealso had no dynamic per-handshake certificate selector at all: its only SNI mechanism was the statictls: [{ serverName }, ...]array registered up front.tls.createServerdoes honorSNICallbackbecause it goes throughBun.listen/node:tls, which already has the resolver machinery.Fix
Expose the per-handshake certificate selector that
node:tlsalready uses to the uWSAppthatBun.serveruns on, and forward thenode:httpsSNICallbackinto it.node:tls/Bun.listen(us_listen_socket_on_server_name+us_socket_sni_resolve, driven from the BoringSSL select-certificate callback). This adds a resolver hook on the uWSApp/HttpContextDataand routes it through the sameus_select_cert_cbpath, so a synchronous pick, an asynchronous suspend, and a refusal all behave identically to thenode:tlsserver.resumeServerSNI(token, context, isError)completes it, instead of falling through to the default context. A small per-thread registry keyed by an opaque token tracks suspended handshakes; the entry is dropped when the handshake'sSSLis freed (which always precedes the socket's own free), so a resolution that outlives its connection is a safe no-op.tlsClientError, matching Node and the existingnode:tlsbehavior.Verification
test/js/node/http/node-https-sni.test.tscovers: per-connection certificate selection, serving a request through the selected cert, synchronous refusal (error / invalid context / throw), fall-through to the default, asynchronous suspend-and-resume, asynchronous error, and a connection destroyed while its callback is still pending.The tests fail against the released binary (
USE_SYSTEM_BUN=1) and pass with this build. The existingnode:tlsserver SNI suite (node-tls-server.test.ts) and thenode:tlsparallel SNI tests still pass.Related issues
Implements
SNICallback/ dynamic SNI forBun.serveandnode:https:Fixes #4053
Fixes #6749
Fixes #14395
Also relevant (not auto-closed here): #17932 covers the
node:tlsserver whereSNICallbackalready fires and adds a separateALPNCallbackrequest; #31580 tracks thenode:tlsSNICallback error path from #31155. This PR makes thenode:httpsserver behave the same way for both.