Skip to content

types: declare allowPartialTrustChain, sessionTimeout, sigalgs and ecdhCurve on Bun.TLSOptions - #38134

Open
robobun wants to merge 1 commit into
mainfrom
farm/810dc4d8/tls-options-types
Open

types: declare allowPartialTrustChain, sessionTimeout, sigalgs and ecdhCurve on Bun.TLSOptions#38134
robobun wants to merge 1 commit into
mainfrom
farm/810dc4d8/tls-options-types

Conversation

@robobun

@robobun robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Problem

Fix

  • packages/bun-types/bun.d.ts: add the four members to Bun.TLSOptions. BunFetchRequestInitTLS extends it and the serve/listen/connect/WebSocket option types reference it, so one declaration covers every API.
  • Value types are the ones the converter enforces, checked on the released binary: allowPartialTrustChain is a strict boolean (1 throws ERR_INVALID_ARG_TYPE), sessionTimeout an integer number (1.5 throws ERR_OUT_OF_RANGE, "300" throws ERR_INVALID_ARG_TYPE), sigalgs and ecdhCurve strings (42 / 7 throw ERR_INVALID_ARG_TYPE); explicit undefined is accepted for all four, hence | undefined.
  • Each JSDoc claim was checked by running the option through the Bun APIs on the released binary (transcript below): the partial chain verifies on fetch, Bun.connect and a requestCert Bun.serve only with the flag; sigalgs accepts both TLS 1.3 names and RSA-PSS+SHA256 style pairs and fails the handshake without an overlap on either side; ecdhCurve accepts single names, aliases, colon lists and "auto", and fails the handshake without a shared group; sessionTimeout: 7 makes the server's TLS 1.2 tickets advertise a 7 second lifetime.
  • sessionTimeout is documented as applying to TLS 1.2 sessions because that is what the runtime does today: SSL_CTX_set_timeout only sets the TLS <= 1.2 lifetime in BoringSSL, and TLS 1.3 tickets keep the 2 day default (172800 below, where Node prints 7). That runtime gap is a separate fix in usockets; when it lands the sentence in the JSDoc goes away. Declaring the member now is still right: the option exists, is validated, and does what the JSDoc says.
  • docs/runtime/http/server.mdx: list the four options in the TLSOptions reference block.
  • Verification:
    • test/integration/bun-types/bun-types.test.ts, new Bun.TLSOptions case: type-checks the four members (and rejects the wrong value types) on Bun.TLSOptions itself, on Bun.serve (single and per-serverName array), Bun.listen, Bun.connect, fetch and WebSocket. It spawns tsc over one file, so it runs on debug builds too; the existing Bun.mmap case now shares that helper. Without the bun.d.ts hunk it fails with 16 diagnostics; bun bd test test/integration/bun-types/bun-types.test.ts passes with it.
    • test/integration/bun-types/fixture/{serve,tcp,fetch}.ts: usage lines so the release-only whole-fixture checks cover the members too. With a release build the full file passes (16/16, including checks with lib.dom.d.ts and tsgo); without the bun.d.ts hunk those cases fail with 17 diagnostics across the three fixtures.

Background

  • Bun.TLSOptions is the single TypeScript interface behind the tls option of Bun.serve, Bun.listen, Bun.connect, fetch, the WebSocket client and the SQL/Redis clients. At runtime all of them convert that object with SSLConfig::from_js, generated from SSLConfig.bindv2.ts, so the interface is meant to mirror that file's member list.
  • A partial trust chain is a certificate chain that ends at an intermediate CA instead of a self-signed root. OpenSSL/BoringSSL normally insist on reaching a self-signed certificate in the trust store (UNABLE_TO_GET_ISSUER_CERT otherwise); X509_V_FLAG_PARTIAL_CHAIN lets any certificate in the store act as the anchor. Node added the same option in v22.9.
  • sigalgs configures the TLS signature algorithms: BoringSSL's SSL_CTX_set1_sigalgs_list sets both the list this side signs with and the list it accepts from the peer, which is why a server whose key is RSA but whose sigalgs only names ECDSA schemes fails every handshake.
  • ecdhCurve is Node's name for the list of key agreement groups (SSL_CTX_set1_groups_list); the handshake needs one group both sides list. "auto" is Node's documented default and SSLConfig.rs:187-193 maps it to "leave BoringSSL's default list alone".
  • test/integration/bun-types packs packages/bun-types and type-checks the fixture/ directory against it with and without lib.dom. Those whole-fixture cases run on release builds only (the in-process TypeScript service is too slow under a debug build), which is why the new coverage also includes a single-file tsc case that runs everywhere.
Runtime probes on the released binary (bun 1.4.0, Node key fixtures; agent6 is issued by the ca3 intermediate, ca3 by the ca1 root, agent1 has an RSA key)

allowPartialTrustChain, trusting only ca3:

fetch        allowPartialTrustChain=false: rejected: UNABLE_TO_GET_ISSUER_CERT
fetch        allowPartialTrustChain=true:  HTTP 200
Bun.connect  allowPartialTrustChain=false: authorizationError=UNABLE_TO_GET_ISSUER_CERT
Bun.connect  allowPartialTrustChain=true:  authorizationError=ERR_TLS_CERT_ALTNAME_INVALID   (chain ok; agent6 has no SAN for 127.0.0.1)
Bun.serve    allowPartialTrustChain=false: rejected: ECONNRESET     (requestCert: true, client presents the agent6 leaf)
Bun.serve    allowPartialTrustChain=true:  HTTP 200
allowPartialTrustChain: 1 / "yes" -> ERR_INVALID_ARG_TYPE: TLSOptions.allowPartialTrustChain must be a boolean

sigalgs (server key is RSA):

fetch   (default):                                  HTTP 200
fetch   sigalgs: "rsa_pss_rsae_sha256":             HTTP 200
fetch   sigalgs: "RSA-PSS+SHA256":                  HTTP 200
fetch   sigalgs: "ECDSA+SHA256:RSA-PSS+SHA256":     HTTP 200
fetch   sigalgs: "ecdsa_secp256r1_sha256":          handshake fails
fetch   sigalgs: 42:                                ERR_INVALID_ARG_TYPE
serve   sigalgs: "rsa_pss_rsae_sha256", default client:     HTTP 200
serve   sigalgs: "ecdsa_secp256r1_sha256", default client:  handshake fails
serve   sigalgs: "not-a-sigalg" / "":   throws ERR_SSL_INVALID_SIGNATURE_ALGORITHM
serve   sigalgs: 42:                    throws ERR_INVALID_ARG_TYPE: TLSOptions.sigalgs must be a string

ecdhCurve (server pinned to P-384):

fetch   (default):                 HTTP 200
fetch   ecdhCurve: "P-384":        HTTP 200
fetch   ecdhCurve: "secp384r1":    HTTP 200
fetch   ecdhCurve: "auto":         HTTP 200
fetch   ecdhCurve: "X25519:P-256": handshake fails
fetch   ecdhCurve: 7:              ERR_INVALID_ARG_TYPE
serve   ecdhCurve: "auto" / "X25519:P-256:P-384" / "prime256v1" / "X25519MLKEM768": accepted
serve   ecdhCurve: "not-a-curve" / "":  throws ERR_SSL_UNSUPPORTED_ELLIPTIC_CURVE
serve   ecdhCurve: 7:                   throws ERR_INVALID_ARG_TYPE: TLSOptions.ecdhCurve must be a string

sessionTimeout (ticket lifetime hint seen by openssl s_client, server configured with sessionTimeout: 7):

bun  (Bun.serve and node:tls)   TLSv1.2: 7     TLSv1.3: 172800
node v26.3.0                    TLSv1.2: 7     TLSv1.3: 7
unset                           TLSv1.2: 7200  TLSv1.3: 172800
sessionTimeout: 1.5   -> ERR_OUT_OF_RANGE: TLSOptions.sessionTimeout must be an integer (received 1.5)
sessionTimeout: "300" -> ERR_INVALID_ARG_TYPE: TLSOptions.sessionTimeout must be a number

Bun.listen + Bun.connect and new WebSocket(url, { tls }) with all four options set: handshake completes / socket opens.

Type-check failure on main, the new test's tsc output (trimmed):

tls-options-context-options.ts(1,4): error TS2353: ... 'allowPartialTrustChain' does not exist in type 'TLSOptions'.
tls-options-context-options.ts(32,14): error TS2353: ... 'allowPartialTrustChain' does not exist in type 'TLSOptions[] | TLSOptions'.
tls-options-context-options.ts(52,14): error TS2769: No overload matches this call.        (Bun.listen)
tls-options-context-options.ts(64,14): error TS2769: No overload matches this call.        (Bun.connect)
tls-options-context-options.ts(73,14): error TS2769: No overload matches this call.        (fetch)
tls-options-context-options.ts(80,12): error TS2769: No overload matches this call.        (WebSocket)
(16 diagnostics in total)

…dhCurve on Bun.TLSOptions

The runtime has accepted these four members of a `tls` object since the
SSLConfig converter gained them, on every API that builds a TLS context
through it (Bun.serve, Bun.listen, Bun.connect, fetch, WebSocket, and the
SQL/Redis clients). The TypeScript interface never gained them, so passing
any of them is a TS2353 excess-property error.

Declare them with the value types the converter enforces (strict boolean,
integer number, string) and document what each one does, including the
current TLS 1.2-only scope of sessionTimeout. List them in the server docs'
TLSOptions reference.

The bun-types integration test gains a single-file tsc case that runs on
debug builds too (the Bun.mmap case now shares the helper), and the serve,
tcp and fetch fixtures exercise the members so the release-lane checks with
and without lib.dom, and tsgo, cover them as well.
@robobun
robobun requested a review from alii as a code owner August 13, 2026 09:37
@coderabbitai

coderabbitai Bot commented Aug 13, 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: 7 minutes

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: 59786460-987f-479a-959d-9d4dd578dd3a

📥 Commits

Reviewing files that changed from the base of the PR and between b7a0431 and 6fd6f13.

📒 Files selected for processing (6)
  • docs/runtime/http/server.mdx
  • packages/bun-types/bun.d.ts
  • test/integration/bun-types/bun-types.test.ts
  • test/integration/bun-types/fixture/fetch.ts
  • test/integration/bun-types/fixture/serve.ts
  • test/integration/bun-types/fixture/tcp.ts

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

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 9:32 AM PT - Aug 13th, 2026

@robobun, your commit 6fd6f13 has 2 failures in Build #94493 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 38134

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

bun-38134 --bun

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Status: ready for review.

Reproduced on main by type-checking Bun.serve({ fetch: () => new Response(), tls: { key, cert, allowPartialTrustChain: true, sessionTimeout: 300, sigalgs: "rsa_pss_rsae_sha256", ecdhCurve: "P-256" } }) against packages/bun-types (the new Bun.TLSOptions case in test/integration/bun-types/bun-types.test.ts): 16 diagnostics without the bun.d.ts hunk, none with it. Each option was also run through the Bun APIs on the released binary to confirm the declared value types and the JSDoc; the transcript is in the PR description.

While doing that I found that sessionTimeout currently only reaches TLS 1.2 sessions (BoringSSL's SSL_CTX_set_timeout does not cover TLS 1.3 tickets, Node's does). The JSDoc here describes that current behavior; the runtime side is being fixed separately.

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

I reviewed this PR and found no bugs. The four new TLSOptions members match the runtime converter in SSLConfig.bindv2.ts, and the fixture additions are appended so the hardcoded lib.dom.d.ts diagnostic line numbers are unaffected. Since packages/bun-types/ has a CODEOWNER, a human sign-off would still be worthwhile.

What was reviewed

  • Verified allowPartialTrustChain: b.bool, sessionTimeout: b.i32, sigalgs/ecdhCurve: b.String.nullable in src/runtime/socket/SSLConfig.bindv2.ts:100-114 match the declared types.
  • Checked the expectSingleFileToTypeCheck refactor preserves the Bun.mmap test's behavior (same tsconfig, same tsc invocation).
  • Confirmed new fixture lines in fetch.ts/serve.ts/tcp.ts land after existing content and no tcp.ts/serve.ts entries exist in the checks with lib.dom.d.ts diagnostics array.
Extended reasoning...

Overview

This PR adds four optional members — allowPartialTrustChain, sessionTimeout, sigalgs, ecdhCurve — to interface TLSOptions in packages/bun-types/bun.d.ts, plus matching entries in the docs/runtime/http/server.mdx reference block. The runtime already accepts these (declared in src/runtime/socket/SSLConfig.bindv2.ts:100-114); only the type declarations lagged. Test coverage is added via a new single-file tsc case in bun-types.test.ts (which also refactors the existing Bun.mmap case into a shared expectSingleFileToTypeCheck helper) and appended usage lines in the fetch.ts/serve.ts/tcp.ts fixtures.

Security risks

None. This is a .d.ts-only change with no runtime effect; the docs and test fixtures are compile-time-only. The declared types match the strict validation the runtime already performs.

Level of scrutiny

Low. The change is mechanical: it mirrors an existing runtime schema into the type declarations. I verified each declared type against the bindv2 source (b.boolboolean, b.i32number, b.String.nullablestring | undefined). The | undefined suffix on optional members follows the pattern used elsewhere in the same interface. The refactored test helper is byte-for-byte equivalent to the inlined version it replaces. The one thing worth an owner's eye is the JSDoc wording (particularly the TLS 1.2-only caveat on sessionTimeout), which the PR description says was empirically verified but which sets user-facing documentation.

Other factors

packages/bun-types/ and *.d.ts are listed in .github/CODEOWNERS, which per the review policy means I should not auto-approve. The change is otherwise straightforward, thoroughly tested (both a debug-build-runnable single-file check and release-only whole-fixture checks including lib.dom and tsgo), and the PR description documents empirical verification of every JSDoc claim against the released binary. There are no prior human review comments to address.

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

On the one point flagged above, the TLS 1.2 wording in the sessionTimeout JSDoc: that sentence is what the released binary does rather than a guess, and it is quick to check. With Bun.serve({ tls: { key, cert, sessionTimeout: 7 } }), openssl s_client -tls1_2 receives a ticket with lifetime hint: 7, while -tls1_3 (after one request, the tickets follow the first response) receives 172800; Node prints 7 for both. The reason is SSL_CTX_set_timeout in packages/bun-usockets/src/crypto/openssl.c:1326, which BoringSSL documents as the TLS 1.2-and-earlier lifetime; TLS 1.3 tickets take session_psk_dhe_timeout, which nothing sets. The full transcript is in the description. Once the runtime applies the option to TLS 1.3 as well, that sentence should be dropped; the rest of the declaration is unaffected.

@robobun

robobun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Heads-up on the sessionTimeout JSDoc here: #38145 makes the runtime apply the option to TLS 1.3 sessions as well (it adds the SSL_CTX_set_session_psk_dhe_timeout() call next to SSL_CTX_set_timeout()), so once that lands the "TLS 1.2 sessions ... TLS 1.3 session tickets are not affected" wording should become "sessions of every protocol version" (with BoringSSL's defaults being 2 hours for TLS 1.2 and 2 days for TLS 1.3 when the option is 0). No conflict between the two diffs, this one does not touch bun.d.ts or the docs.

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