Skip to content

node:tls: implement setDefaultCACertificates() - #30570

Closed
robobun wants to merge 2 commits into
mainfrom
farm/06cd3e87/tls-set-default-ca-certificates
Closed

node:tls: implement setDefaultCACertificates()#30570
robobun wants to merge 2 commits into
mainfrom
farm/06cd3e87/tls-set-default-ca-certificates

Conversation

@robobun

@robobun robobun commented May 12, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Implements tls.setDefaultCACertificates(certs) (added in Node.js v24.5.0) as a native trust-store override, replacing the JS-only stub from #31155.

Fixes #24340
Fixes #13868

Repro

import tls from 'node:tls';
tls.setDefaultCACertificates(tls.getCACertificates('system'));

Before: TypeError: tls.setDefaultCACertificates is not a function
After: replaces the default trust store with the system CA set.

How

  • packages/bun-usockets/src/crypto/root_certs.cpp adds a mutex-guarded user-override STACK_OF(X509)*. When present, us_get_default_ca_store() builds the store exclusively from it (Node.js does not merge bundled/system/extra back in). The cached shared store moves from std::call_once to a mutex so setDefaultCACertificates can invalidate it for subsequent connections; in-flight SSL*s keep their own X509_STORE reference via SSL_set0_verify_cert_store.
  • packages/bun-usockets/src/crypto/openssl.c: https.request() / fetch() build their HTTPS SSL_CTX once on the HTTP thread and reuse it for the process lifetime, so a JS-side injection at createSecureContext time can't reach them. us_internal_ssl_attach() now checks us_has_user_root_certs() and, for any CTX not marked with us_ctx_user_ca_ex_idx (i.e. no explicit ca), overrides the verify store per-SSL with the current shared default. CTXs built with an explicit ca are left untouched.
  • src/jsc/bindings/NodeTLS.cpp: resetRootCertStore accepts an array of PEM strings / ArrayBufferViews (each entry may contain multiple certs), deduplicates by X509_cmp identity, and installs the result. getUserRootCertificates returns undefined when no override is installed (so the JS side falls back to bundled/system/extra) or a frozen PEM array otherwise; the snapshot is taken under the root-cert mutex so a concurrent Worker swapping certs can't free them mid-serialise.
  • src/js/node/tls.ts: setDefaultCACertificates(certs) validates certs is an Array of string | ArrayBufferView (ERR_INVALID_ARG_TYPE with Node-compatible wording), calls into native, and invalidates the JS-side cache. getCACertificates('default') queries native for the override on every call so a Worker sees an override installed on another thread. Removes the JS-side _defaultCACertificatesOverride hook from net,tls: port Node.js net/tls compatibility tests and fix the gaps they surface — half-open/reset/write semantics, server TLSSocket wrap, session/keylog, SNICallback/ALPNCallback, pfx, OpenSSL error shapes, addCACert, local binding (+305 tests) #31155 (the native store now handles every path, including fetch() and long-lived SSL_CTXs that the JS hook could not reach).

Node.js parity notes

  • Deduplicates supplied certificates (same cert passed three times → one entry).
  • Empty array is a valid override that makes new default-CA connections fail verification.
  • Invalid PEM throws ERR_CRYPTO_OPERATION_FAILED and leaves the current defaults untouched.
  • getCACertificates('bundled') / 'system' / 'extra' remain unchanged.
  • Node.js scopes the override per-thread (thread_local); this implementation is process-global, guarded by a mutex so Workers don't race each other. getCACertificates('default') reflects the process-global override from any Worker.

Tests

test/js/node/tls/node-tls-set-default-ca-certificates.test.ts covers: existence, input validation (non-array / bad element types), round-tripping through getCACertificates('default'), deduplication, Buffer/Uint8Array/DataView inputs, invalid-PEM rollback, Worker visibility, and an end-to-end HTTPS sequence of fail (no CA) → install CA → succeed → clear CA → fail that proves the cached HTTP-thread SSL_CTX picks up runtime changes.

All seven upstream test/js/node/test/parallel/test-tls-set-default-ca-certificates-*.js and all ten test-tls-get-ca-certificates-*.js pass unmodified.

test/js/node/tls/ssl-ctx-cache.test.ts's "ca: [] skips the override" case is updated: ca: [] collapses to no-CA at the native SSLConfig boundary ([] → None), so it now behaves like omitting ca and the process default applies. The test now asserts that, plus a new case for ca: [nonMatchingCA] to prove an explicit ca still wins over the default. Node's full "ca: [] = empty trust store" semantics remain a follow-up (same as before this PR; the previous behaviour was also not Node-correct, per the test's own comment).


This also provides the "runtime hook to add a CA cert" that #13868 asks for: in a standalone executable you can embed a PEM string and call tls.setDefaultCACertificates([...tls.getCACertificates('bundled'), myCA]) at startup.


Rebase note (squashed onto main at 0c537fe): #31155 landed a JS-only setDefaultCACertificates that injected _defaultCACertificatesOverride as the ca option inside InternalSecureContext, plus its own us_ctx_user_ca_ex_idx SSL_CTX marker. The conflict resolution:

  • openssl.c: dropped this PR's separate us_ctx_default_ca_ex_idx marker in favour of the inverse check !us_ctx_user_ca_ex_idx that main already defines; the per-SSL refresh in us_internal_ssl_attach now gates on that.
  • src/js/node/tls.ts: removed the _defaultCACertificatesOverride JS hook and its injection sites in InternalSecureContext/setSecureContext since the native store override covers every construction path (including the cached HTTPS SSL_CTX and fetch(), which the JS hook could not reach).
  • test/js/node/test/common/tls.js: kept assertEqualCerts comparing via JSON.stringifyd metadata (a Set of raw objects compares by reference).

@robobun

robobun commented May 12, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 5:19 AM PT - Jun 17th, 2026

@robobun, your commit cb396e9 has 1 failures in Build #63126 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 30570

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

bun-30570 --bun

@github-actions

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Ability to set NODE_EXTRA_CA_CERTS in standalone executable #13868 - Requests a runtime hook to set custom CA certificates in standalone executables; tls.setDefaultCACertificates() provides exactly that API

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

Fixes #13868

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented May 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds process-global, mutex-protected user override for the default TLS CA store with native setter/getter, JSC host functions for reset/read (PEM parsing and dedupe), TypeScript binding tls.setDefaultCACertificates(), and comprehensive tests.

Changes

User-Overridable Default CA Certificates

Layer / File(s) Summary
Native C++ Storage and APIs (uSockets)
packages/bun-usockets/src/crypto/root_certs.cpp, packages/bun-usockets/src/crypto/root_certs_header.h, packages/bun-usockets/src/crypto/openssl.c
Process-global mutex-protected storage for user-overridden CA certificates, exported us_set_user_root_certs()/inspection APIs, a locked default-store builder that respects overrides, an invalidatable cached shared X509_STORE, and SSL_CTX marking + per-SSL verify-store override when applicable.
JSC Host Functions and PEM Parsing
src/jsc/bindings/NodeTLS.cpp, src/jsc/bindings/NodeTLS.h
Adds resetRootCertStore and getUserRootCertificates host functions: accept JS strings or ArrayBufferView, parse PEM bundles with OpenSSL error handling, deduplicate certificates by DER/X509 identity, commit or clear the native override, and return frozen arrays of PEM strings for snapshots.
TypeScript Module API and Validation
src/js/node/tls.ts
Exports setDefaultCACertificates(certs) on node:tls, validates the argument as an array of strings/ArrayBufferView, calls the native reset binding, clears VM-local cached default certificates, and consults the native getUserRootCertificates when caching defaults.
Certificate Comparison Test Utilities
test/js/node/test/common/tls.js
Adds extractMetadata, assertEqualCerts, and includesCert helpers that use crypto.X509Certificate to compare certificates by serialNumber, issuer, and subject.
Comprehensive Test Suite
test/js/node/tls/node-tls-set-default-ca-certificates.test.ts, test/js/node/test/parallel/test-tls-set-default-ca-certificates-*.js
New tests (spawned in subprocesses) that validate API presence, input/type errors (index-specific messages), empty-array clearing, deduplication, supported input types (Buffer, Uint8Array, DataView), invalid PEM handling without mutating prior state, worker visibility, and that overriding the default trust set affects TLS/HTTPS verification.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: implementing the setDefaultCACertificates() API for the node:tls module.
Linked Issues check ✅ Passed The PR successfully addresses both #24340 (implementing setDefaultCACertificates API) and #13868 (runtime hook for custom CAs in standalone executables) through native bindings, proper input validation, deduplication, error handling, and comprehensive tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing setDefaultCACertificates and supporting infrastructure: native C++ modifications for cert management, JSC bindings, TypeScript wrapper, test utilities, and integration tests—all aligned with the stated objectives.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering all required sections with clear explanations of the changes, implementation details, test coverage, and Node.js parity notes.

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


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

@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: 3

🤖 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/root_certs.cpp`:
- Around line 193-205: The override state is currently process-global
(shared_store_mutex, shared_store, user_root_certs, has_user_root_certs) which
allows one Worker to affect all Workers; change these globals to be thread-local
so the override is scoped per Worker/thread: replace the static globals with
thread_local equivalents (e.g., thread_local X509_STORE *shared_store,
thread_local STACK_OF(X509)* user_root_certs, thread_local bool
has_user_root_certs) and remove or narrow the use of shared_store_mutex
accordingly so that us_get_default_ca_store() and
us_get_shared_default_ca_store() operate on per-thread stores and no longer
silently change TLS verification for other Workers. Ensure any
initialization/cleanup paths that reference those symbols use the thread-local
variants.

In `@src/jsc/bindings/NodeTLS.cpp`:
- Around line 277-300: The loop currently walking STACK_OF(X509)* certs returned
by us_get_user_root_certs() can race with us_set_user_root_certs(); take a
stable snapshot under the same lock used by us_set_user_root_certs() (or perform
PEM serialization while holding that lock) before iterating: acquire the lock,
copy or up-ref each X509 (or serialize to PEM while locked) so
sk_X509_num/sk_X509_value are safe, then release the lock and continue the
BIO/PEM conversion and the existing error handling (references:
us_get_user_root_certs, us_set_user_root_certs, certs, sk_X509_num,
sk_X509_value, BIO_new/BIO_free, PEM_write_bio_X509, throwOutOfMemoryError,
throwError).

In `@test/js/node/tls/node-tls-set-default-ca-certificates.test.ts`:
- Around line 174-179: The test currently only checks lengths after rollback
which can false-pass; change the assertions to verify the actual certificates
were restored by comparing the certificate arrays directly (use deep equality
between the variables before and after returned from
tls.getCACertificates("default")) or, alternatively, compare extracted cert
metadata (e.g., fingerprint/subject fields) from before and after to ensure the
rollback restored the exact same cert set; update the assertions around
tls.getCACertificates("default") and tls.setDefaultCACertificates(["not a
certificate"]) accordingly (replace assert.strictEqual(after.length,
before.length) with a deep comparison of before and after or their metadata).
🪄 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: 528db0aa-00c9-43cc-b433-8367014e6e01

📥 Commits

Reviewing files that changed from the base of the PR and between 314ffe3 and 3a8dc34.

📒 Files selected for processing (7)
  • packages/bun-usockets/src/crypto/root_certs.cpp
  • packages/bun-usockets/src/crypto/root_certs_header.h
  • src/js/node/tls.ts
  • src/jsc/bindings/NodeTLS.cpp
  • src/jsc/bindings/NodeTLS.h
  • test/js/node/test/common/tls.js
  • test/js/node/tls/node-tls-set-default-ca-certificates.test.ts

Comment thread packages/bun-usockets/src/crypto/root_certs.cpp Outdated
Comment thread src/jsc/bindings/NodeTLS.cpp Outdated
Comment thread test/js/node/tls/node-tls-set-default-ca-certificates.test.ts Outdated

@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

♻️ Duplicate comments (1)
src/jsc/bindings/NodeTLS.cpp (1)

277-300: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Take a stable snapshot before walking user_root_certs.

This still iterates the raw STACK_OF(X509)* returned by us_get_user_root_certs() with no synchronization. A concurrent tls.setDefaultCACertificates() can free or replace that stack mid-loop and turn sk_X509_value / PEM_write_bio_X509 into a use-after-free.

🤖 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/jsc/bindings/NodeTLS.cpp` around lines 277 - 300, The code currently
iterates the raw STACK_OF(X509)* from us_get_user_root_certs() (using
sk_X509_num/sk_X509_value and calling PEM_write_bio_X509) without
synchronization, risking use-after-free if tls.setDefaultCACertificates()
replaces or frees the stack; to fix, take a stable snapshot immediately after
calling us_get_user_root_certs(): copy the X509* entries into a local container
(or call X509_up_ref on each and push into std::vector<X509*>) and then iterate
that snapshot when calling PEM_write_bio_X509/sk_X509_value; after the loop
release the references (X509_free) or clear the vector; handle nullptr/zero-size
the same as before and preserve existing error/BIO handling (functions
referenced: us_get_user_root_certs, sk_X509_num, sk_X509_value,
PEM_write_bio_X509).
🤖 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 `@src/jsc/bindings/NodeTLS.cpp`:
- Around line 195-200: The CA override is currently process-global via the
static symbols user_root_certs and shared_store_mutex, causing one Worker to
affect all Workers; change the implementation so the override is scoped
per-Worker (as Node.js does) by moving user_root_certs and its mutex out of
static globals into per-Worker storage (e.g., thread_local or a member on the
Worker/Isolate object) and update us_set_user_root_certs, any getters/setters,
and code paths that reference shared_store_mutex to operate on the per-Worker
variable/lock instead; ensure initialization, cleanup, and access patterns
mirror Node.js semantics (explicit empty override vs no override) and that
concurrent access is protected by the per-Worker mutex.
- Around line 151-156: The function appendX509sFromPEM performs an unchecked
cast of data.size() to int for BIO_new_mem_buf; add a guard at the top of
appendX509sFromPEM that rejects inputs where data.size() > INT_MAX before
calling BIO_new_mem_buf (and before the size_t→int cast), set/put an OpenSSL
error (e.g., via ERR_put_error) and return a non-zero error code (like
ERR_peek_last_error()) so oversized buffers are rejected safely; reference
appendX509sFromPEM, data.size(), BIO_new_mem_buf, and INT_MAX when locating the
change.

---

Duplicate comments:
In `@src/jsc/bindings/NodeTLS.cpp`:
- Around line 277-300: The code currently iterates the raw STACK_OF(X509)* from
us_get_user_root_certs() (using sk_X509_num/sk_X509_value and calling
PEM_write_bio_X509) without synchronization, risking use-after-free if
tls.setDefaultCACertificates() replaces or frees the stack; to fix, take a
stable snapshot immediately after calling us_get_user_root_certs(): copy the
X509* entries into a local container (or call X509_up_ref on each and push into
std::vector<X509*>) and then iterate that snapshot when calling
PEM_write_bio_X509/sk_X509_value; after the loop release the references
(X509_free) or clear the vector; handle nullptr/zero-size the same as before and
preserve existing error/BIO handling (functions referenced:
us_get_user_root_certs, sk_X509_num, sk_X509_value, PEM_write_bio_X509).
🪄 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: 69012620-6f50-4000-9ffa-31759fb3854d

📥 Commits

Reviewing files that changed from the base of the PR and between 3a8dc34 and b6447bd.

📒 Files selected for processing (1)
  • src/jsc/bindings/NodeTLS.cpp

Comment thread src/jsc/bindings/NodeTLS.cpp
Comment thread src/jsc/bindings/NodeTLS.cpp
Comment thread packages/bun-usockets/src/crypto/root_certs.cpp Outdated
Comment thread src/js/node/tls.ts Outdated

@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

♻️ Duplicate comments (1)
src/jsc/bindings/NodeTLS.cpp (1)

151-156: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard the size_tint cast before BIO_new_mem_buf().

BIO_new_mem_buf() takes a signed length. If data.size() > INT_MAX, the cast wraps negative and BoringSSL/OpenSSL switches to NUL-terminated semantics, so an oversized ArrayBufferView can read past the provided buffer. Reject oversized inputs before the cast and surface the existing crypto failure path.

🤖 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/jsc/bindings/NodeTLS.cpp` around lines 151 - 156, In appendX509sFromPEM,
guard the size_t→int cast before calling BIO_new_mem_buf by checking if
data.size() > INT_MAX and rejecting oversized inputs via the same crypto failure
path (i.e., return the existing error code used on failure such as
ERR_peek_last_error()) so the cast is never performed and BIO_new_mem_buf is not
given a wrapped negative length; adjust the logic around
BIO_new_mem_buf/BIO_new_mem_buf(data.data(), static_cast<int>(data.size()))
accordingly.
🤖 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 `@src/jsc/bindings/NodeTLS.cpp`:
- Around line 159-163: The loop that reads certs with PEM_read_bio_X509 and
calls sk_X509_push currently ignores sk_X509_push's return value, so on
allocation failure the code still increments pushed (misleading count) and in
the other location calls X509_up_ref before pushing (causing leaks when push
fails); update both sites to check sk_X509_push() and treat a return of 0 as an
error: if push fails, free the newly created X509 (and if X509_up_ref was
already called, decrement/refclean appropriately), pop and free any certs
already pushed and free the stack, then return/report an error instead of
continuing; reference the PEM_read_bio_X509 loop, the sk_X509_push calls,
X509_up_ref usage, and the us_set_user_root_certs call when implementing the
unwind and error handling.

In `@test/js/node/tls/node-tls-set-default-ca-certificates.test.ts`:
- Around line 219-228: The failing assertion uses a different hostname for req2
which can mask hostname-mismatch or connection errors; change the https.request
options for req2 to reuse "localhost" (or include servername: "localhost") so
the same host is used, and then only treat the test as passing if the emitted
error is a TLS verification failure (e.g., check err.code/err.message for a
TLS/certificate verification indicator) rather than any connection error; update
the req2 creation and its error assertion around
tls.setDefaultCACertificates([]), referencing tls.setDefaultCACertificates,
https.request/req2, and the error handler to enforce verification-specific
failure.

---

Duplicate comments:
In `@src/jsc/bindings/NodeTLS.cpp`:
- Around line 151-156: In appendX509sFromPEM, guard the size_t→int cast before
calling BIO_new_mem_buf by checking if data.size() > INT_MAX and rejecting
oversized inputs via the same crypto failure path (i.e., return the existing
error code used on failure such as ERR_peek_last_error()) so the cast is never
performed and BIO_new_mem_buf is not given a wrapped negative length; adjust the
logic around BIO_new_mem_buf/BIO_new_mem_buf(data.data(),
static_cast<int>(data.size())) accordingly.
🪄 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: 7ce79f9f-5824-4305-98c5-b16629dfa1e9

📥 Commits

Reviewing files that changed from the base of the PR and between b6447bd and ac2faee.

📒 Files selected for processing (4)
  • packages/bun-usockets/src/crypto/root_certs.cpp
  • packages/bun-usockets/src/crypto/root_certs_header.h
  • src/jsc/bindings/NodeTLS.cpp
  • test/js/node/tls/node-tls-set-default-ca-certificates.test.ts

Comment thread src/jsc/bindings/NodeTLS.cpp
Comment thread test/js/node/tls/node-tls-set-default-ca-certificates.test.ts Outdated

@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: 1

♻️ Duplicate comments (1)
src/jsc/bindings/NodeTLS.cpp (1)

162-165: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Check sk_X509_push() and unwind on failure.

Line 163 and Line 265 ignore stack insertion failure. If the stack grow fails, the parse path leaks x, and the dedupe path leaks the extra X509_up_ref() while still committing a truncated CA set. Treat a falsey sk_X509_push() as fatal and clean up before returning.

Suggested fix
 while (X509* x = PEM_read_bio_X509(bio, nullptr, [](char*, int, int, void*) -> int { return 0; }, nullptr)) {
-    sk_X509_push(out, x);
+    if (!sk_X509_push(out, x)) {
+        X509_free(x);
+        BIO_free(bio);
+        while (pushed-- > 0) {
+            X509_free(sk_X509_pop(out));
+        }
+        return ERR_peek_last_error();
+    }
     pushed++;
 }
 for (int i = 0; i < (int)sk_X509_num(parsed); i++) {
     X509* cert = sk_X509_value(parsed, i);
     if (seen.insert(cert).second) {
         X509_up_ref(cert);
-        sk_X509_push(deduped, cert);
+        if (!sk_X509_push(deduped, cert)) {
+            X509_free(cert); // balance X509_up_ref
+            sk_X509_pop_free(deduped, X509_free);
+            freeParsed();
+            throwOutOfMemoryError(globalObject, scope);
+            return {};
+        }
     }
 }

Also applies to: 261-266

🤖 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/jsc/bindings/NodeTLS.cpp` around lines 162 - 165, The loop that calls
PEM_read_bio_X509 and then sk_X509_push does not handle a falsey sk_X509_push
result, which can leak the newly read X509* (from PEM_read_bio_X509) and, in the
dedupe path, leak the extra X509_up_ref() and commit a truncated CA set; modify
the code in NodeTLS.cpp around the PEM_read_bio_X509 loop and the dedupe/commit
path to treat sk_X509_push failure as fatal: on any sk_X509_push() == 0,
immediately free the pushed X509* (and any already-pushed stack members), undo
any X509_up_ref() increments, clear/ free the target STACK_OF(X509)* (out) and
return an error/abort the parse rather than proceeding to dedupe/commit so no
leaked X509 or inconsistent CA set remains.
🤖 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 `@src/jsc/bindings/NodeTLS.cpp`:
- Around line 168-179: The code currently treats PEM_R_NO_START_LINE as EOF
whenever a certificate was parsed (pushed>0) but does not validate trailing
bytes; update the branch that checks ERR_GET_LIB(last)==ERR_LIB_PEM &&
ERR_GET_REASON(last)==PEM_R_NO_START_LINE so that before calling
ERR_clear_error() and returning success it inspects the remaining input buffer
used by this parsing routine (the buffer pointer/remaining length variables in
this function) and verifies all remaining bytes are empty or whitespace only; if
any non-whitespace data remains, do not clear the error or return success—let
the error path run (which rolls back via the pushed/sk_X509_pop/X509_free loop)
so malformed trailing data causes failure.

---

Duplicate comments:
In `@src/jsc/bindings/NodeTLS.cpp`:
- Around line 162-165: The loop that calls PEM_read_bio_X509 and then
sk_X509_push does not handle a falsey sk_X509_push result, which can leak the
newly read X509* (from PEM_read_bio_X509) and, in the dedupe path, leak the
extra X509_up_ref() and commit a truncated CA set; modify the code in
NodeTLS.cpp around the PEM_read_bio_X509 loop and the dedupe/commit path to
treat sk_X509_push failure as fatal: on any sk_X509_push() == 0, immediately
free the pushed X509* (and any already-pushed stack members), undo any
X509_up_ref() increments, clear/ free the target STACK_OF(X509)* (out) and
return an error/abort the parse rather than proceeding to dedupe/commit so no
leaked X509 or inconsistent CA set remains.
🪄 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: 82a9b761-353f-4264-8e80-f985b0e97368

📥 Commits

Reviewing files that changed from the base of the PR and between ac2faee and 6f39ab5.

📒 Files selected for processing (1)
  • src/jsc/bindings/NodeTLS.cpp

Comment thread src/jsc/bindings/NodeTLS.cpp

@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

♻️ Duplicate comments (2)
src/jsc/bindings/NodeTLS.cpp (1)

168-173: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle sk_X509_push failures in both parse and dedupe paths.

Line 170 and Line 272 ignore push failures. That can leak cert refs and leave partial state installed on allocation failure.

Proposed fix
-    while (X509* x = PEM_read_bio_X509(bio, nullptr, [](char*, int, int, void*) -> int { return 0; }, nullptr)) {
-        sk_X509_push(out, x);
-        pushed++;
+    while (X509* x = PEM_read_bio_X509(bio, nullptr, [](char*, int, int, void*) -> int { return 0; }, nullptr)) {
+        if (!sk_X509_push(out, x)) {
+            X509_free(x);
+            OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+            break;
+        }
+        pushed++;
     }
...
         if (seen.insert(cert).second) {
             X509_up_ref(cert);
-            sk_X509_push(deduped, cert);
+            if (!sk_X509_push(deduped, cert)) {
+                X509_free(cert); // undo up_ref
+                sk_X509_pop_free(deduped, X509_free);
+                freeParsed();
+                throwOutOfMemoryError(globalObject, scope);
+                return {};
+            }
         }
#!/bin/bash
rg -nC3 'sk_X509_push\(out, x\)|X509_up_ref\(cert\)|sk_X509_push\(deduped, cert\)' src/jsc/bindings/NodeTLS.cpp

Also applies to: 268-274

🤖 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/jsc/bindings/NodeTLS.cpp` around lines 168 - 173, The loops that call
PEM_read_bio_X509(...) then sk_X509_push(out, x) (and the dedupe path that calls
sk_X509_push(deduped, cert) and X509_up_ref(cert)) must check sk_X509_push's
return value and handle failures: if sk_X509_push returns 0 (failure)
immediately free the newly-read X509 (X509_free(x)) or drop the up-ref
(X509_free(cert) when you failed after X509_up_ref), free the BIO (BIO_free) and
clean up the entire stack being built (iterate and X509_free any already-pushed
certs and sk_X509_free the stack) before signalling/returning the error to avoid
leaking cert refs or leaving partial state; update the PEM_read_bio_X509 loop
and the dedupe block (where X509_up_ref and sk_X509_push are used) to perform
these checks and cleanup paths.
test/js/node/tls/node-tls-set-default-ca-certificates.test.ts (1)

262-263: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Assert verification-specific failure, not “any error code”.

Line 262 and Line 284 currently pass for any coded error (including non-TLS-path failures), which can mask regressions in the CA override behavior being tested.

Proposed tightening
-      } catch (err) {
-        assert(err.code && err.code !== "ERR_ASSERTION", "step1: " + err.message);
+      } catch (err) {
+        const code = String(err?.code || "");
+        assert(/UNABLE_TO_|SELF_SIGNED|CERT/i.test(code), "step1: expected TLS verify failure, got " + (err?.message || err));
       }
...
-      } catch (err) {
-        assert(err.code && err.code !== "ERR_ASSERTION", "step3: " + err.message);
+      } catch (err) {
+        const code = String(err?.code || "");
+        assert(/UNABLE_TO_|SELF_SIGNED|CERT/i.test(code), "step3: expected TLS verify failure, got " + (err?.message || err));
       }

Also applies to: 283-285

🤖 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 `@test/js/node/tls/node-tls-set-default-ca-certificates.test.ts` around lines
262 - 263, The current assertions (the expression using assert(err.code &&
err.code !== "ERR_ASSERTION", "step1: " + err.message); and the similar checks
later) accept any error with a code and thereby mask non-TLS failures; replace
these with assertions that err.code is one of the expected TLS/CA-related error
codes for this test (e.g., check membership in an explicit allowed set like
['ERR_SSL_HANDSHAKE_FAILURE', 'ERR_OSSL_*', 'ERR_TLS_CERT_ALTNAME_INVALID'] or
the concrete codes your runtime emits) and include the original err.message in
the failure text; update both occurrences (the assert expression and the later
similar block) to perform this exact-code check rather than just rejecting
ERR_ASSERTION.
🤖 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 565-570: The code uses the implementation-defined sentinel (void
*)1 when storing ex_data on SSL_CTX; replace this with the address of a static
marker variable to be portable: add a static variable (e.g., a file-static char
or int) and pass its address into SSL_CTX_set_ex_data instead of (void *)1,
updating any checks that compare the stored pointer (related to
us_ctx_default_ca_ex_idx and ssl_context) to compare against that marker's
address.

In `@packages/bun-usockets/src/crypto/root_certs.cpp`:
- Around line 205-211: Replace the racy global bool has_user_root_certs with a
std::atomic<bool> and use relaxed atomics for the cheap racy read/write
semantics: update the declaration of has_user_root_certs to std::atomic<bool>,
include <atomic>, change the read in us_has_user_root_certs to
has_user_root_certs.load(std::memory_order_relaxed) and change any writes (the
assignment currently guarded by the mutex) to has_user_root_certs.store(...,
std::memory_order_relaxed) so the behavior is unchanged but well-defined.

---

Duplicate comments:
In `@src/jsc/bindings/NodeTLS.cpp`:
- Around line 168-173: The loops that call PEM_read_bio_X509(...) then
sk_X509_push(out, x) (and the dedupe path that calls sk_X509_push(deduped, cert)
and X509_up_ref(cert)) must check sk_X509_push's return value and handle
failures: if sk_X509_push returns 0 (failure) immediately free the newly-read
X509 (X509_free(x)) or drop the up-ref (X509_free(cert) when you failed after
X509_up_ref), free the BIO (BIO_free) and clean up the entire stack being built
(iterate and X509_free any already-pushed certs and sk_X509_free the stack)
before signalling/returning the error to avoid leaking cert refs or leaving
partial state; update the PEM_read_bio_X509 loop and the dedupe block (where
X509_up_ref and sk_X509_push are used) to perform these checks and cleanup
paths.

In `@test/js/node/tls/node-tls-set-default-ca-certificates.test.ts`:
- Around line 262-263: The current assertions (the expression using
assert(err.code && err.code !== "ERR_ASSERTION", "step1: " + err.message); and
the similar checks later) accept any error with a code and thereby mask non-TLS
failures; replace these with assertions that err.code is one of the expected
TLS/CA-related error codes for this test (e.g., check membership in an explicit
allowed set like ['ERR_SSL_HANDSHAKE_FAILURE', 'ERR_OSSL_*',
'ERR_TLS_CERT_ALTNAME_INVALID'] or the concrete codes your runtime emits) and
include the original err.message in the failure text; update both occurrences
(the assert expression and the later similar block) to perform this exact-code
check rather than just rejecting ERR_ASSERTION.
🪄 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: 6c01aa59-d7dd-4f76-a20e-b60ffb5b5d5a

📥 Commits

Reviewing files that changed from the base of the PR and between 6f39ab5 and 3b4ca72.

📒 Files selected for processing (8)
  • packages/bun-usockets/src/crypto/openssl.c
  • packages/bun-usockets/src/crypto/root_certs.cpp
  • packages/bun-usockets/src/crypto/root_certs_header.h
  • src/js/node/tls.ts
  • src/jsc/bindings/NodeTLS.cpp
  • test/js/node/test/parallel/test-tls-set-default-ca-certificates-array-buffer.js
  • test/js/node/test/parallel/test-tls-set-default-ca-certificates-basic.js
  • test/js/node/tls/node-tls-set-default-ca-certificates.test.ts

Comment thread packages/bun-usockets/src/crypto/openssl.c Outdated
Comment thread packages/bun-usockets/src/crypto/root_certs.cpp Outdated
Comment thread test/js/node/tls/node-tls-set-default-ca-certificates.test.ts Outdated
Comment thread packages/bun-usockets/src/crypto/root_certs.cpp

@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 didn't find any remaining issues, but this replaces the process-wide TLS trust root set and reworks the shared X509_STORE lifecycle/locking in native code — definitely one for a human reviewer.

Extended reasoning...

Overview

Implements tls.setDefaultCACertificates() across four layers: a mutex-guarded process-global STACK_OF(X509)* override and invalidatable cached X509_STORE in root_certs.cpp; an SSL_CTX ex_data marker plus per-SSL verify-store refresh in openssl.c so cached HTTPS contexts pick up runtime changes; JSC host functions in NodeTLS.cpp for PEM parsing/dedup and snapshot read-back; and the JS wrapper in tls.ts. ~600 lines plus tests.

Security risks

This is the TLS trust-root surface. The override is intentionally process-global (diverging from Node's per-thread semantics), so one Worker can change verification for all. The PR went through several rounds fixing a UAF in the snapshot path, a data race on has_user_root_certs, an X509 refcount leak, and sk_X509_push allocation-failure handling — all now addressed. The remaining design (mutex + atomic flag + SSL_set0_verify_cert_store per-SSL) looks sound to me, but the blast radius of a mistake here is silent mis-verification of TLS peers.

Level of scrutiny

High. Native crypto code with manual BoringSSL refcounting, cross-thread shared mutable state, and a behavioural change to how every default-CA TLS connection builds its trust store (the shared store moved from std::call_once to a mutex-guarded rebuildable cache). The us_internal_ssl_attach change also runs on the hot per-connection path.

Other factors

All prior bot feedback (mine and CodeRabbit's) is resolved. Test coverage is solid — input validation, dedup, rollback-on-error, Worker visibility, and an end-to-end fail→install→succeed→clear→fail HTTPS sequence, plus two upstream Node parallel tests. No outstanding human review comments. Still, the combination of security-critical + concurrency + manual memory management + intentional Node.js divergence is exactly the profile that warrants human sign-off.

@robobun

robobun commented May 12, 2026

Copy link
Copy Markdown
Collaborator Author

Status

Implementation complete at e2136fa. All review threads resolved; claude[bot]'s final review found no remaining issues.

CI

282/286 jobs pass. The 4 failures are all on tests unrelated to this diff (which only touches node:tls / TLS trust-store code paths):

Lane Failing test Relation to this PR
darwin-aarch64-14 test/js/bun/s3/s3-storage-class.test.ts S3 — none
windows-x64-2019 test-http-should-emit-close-when-connection-is-aborted.ts plain HTTP fetch() abort — none
windows-x64-baseline-2019 same HTTP-abort timeout + bun-install-registry.test.ts hoisting (passed on retry) none
windows-aarch64-11 same HTTP-abort timeout + same install-registry flake none

The HTTP-abort test timed out 4/4 attempts on 3 Windows lanes in two consecutive builds (53807 and 53813), so re-rolling is unlikely to help.

The new tests added by this PR (node-tls-set-default-ca-certificates.test.ts, test-tls-set-default-ca-certificates-{basic,array-buffer}.js) passed on every lane that ran them, including linux-x64-asan.

Ready for maintainer review / merge.

@robobun
robobun force-pushed the farm/06cd3e87/tls-set-default-ca-certificates branch from e2136fa to 32d9894 Compare May 25, 2026 23:07

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

No issues found in this pass, but this touches the process-wide TLS trust-root path (mutex-guarded shared X509_STORE, per-SSL verify-store override in us_internal_ssl_attach, intentional process-global vs Node's thread-local semantics) — worth a human sign-off before merge.

Extended reasoning...

Overview

This PR implements tls.setDefaultCACertificates() (Node.js v24.5.0). It spans:

  • packages/bun-usockets/src/crypto/root_certs.cpp — replaces the std::call_once-cached shared default X509_STORE with a mutex-guarded, invalidatable cache; adds process-global user_root_certs override with std::atomic<bool> fast-path flag and a snapshot-under-lock duplicator.
  • packages/bun-usockets/src/crypto/openssl.c — new SSL_CTX ex_data marker so us_internal_ssl_attach() can swap the verify store per-SSL when the default set has changed under a long-lived cached CTX (fetch/https).
  • src/jsc/bindings/NodeTLS.cpp — ~210 new lines: PEM parsing with rollback, X509_cmp-based dedup via std::set, manual sk_X509_* refcounting, two new host functions.
  • src/js/node/tls.ts — JS wrapper, input validation, per-VM cache invalidation that defers to native for cross-Worker visibility.
  • New Bun test, two upstream Node parallel tests, and common/tls.js helpers.

Security risks

This is squarely security-sensitive: it rewires how the default TLS trust root set is selected for every client connection that doesn't pass an explicit ca. Specific concerns a human should weigh:

  • The shared default store moves from immutable-once to mutex-guarded mutable. Every TLS client attach now takes the fast-path branch in us_internal_ssl_attach; correctness depends on the ex_data marker only being set when no user ca was supplied.
  • Manual BoringSSL refcounting (X509_up_ref / sk_X509_pop_free / X509_STORE_add_cert) across two ownership domains; the review cycle already caught one UAF and one leak here.
  • Intentional divergence from Node.js: override is process-global, not per-Worker (thread_local). The rationale (SSL attach runs off the JS thread) is documented and was discussed in-thread, but it's a design decision that affects multi-Worker isolation and should be ratified by a maintainer.

Level of scrutiny

High. This is production-critical crypto/TLS plumbing in C/C++ with concurrency, not a config tweak. The change also affects the hot path for all default-CA TLS connections (the shared-store cache mechanism), not just callers of the new API.

Other factors

  • The PR went through ~7 fix commits during review (UAF snapshot, per-VM cache desync, size_tint cast, sk_X509_push return checks, std::atomic<bool>, static-address marker, redundant X509_up_ref leak, test hostname). All raised threads are resolved and the current diff reflects the fixes.
  • Test coverage is good: input validation, round-trip, dedup, ArrayBufferView variants, invalid-PEM rollback, Worker visibility, and an end-to-end fail→install→succeed→clear→fail HTTPS sequence; plus two unmodified upstream Node tests.
  • CI: 282/286 passing; the 4 failures are documented as unrelated flakes.
  • No CODEOWNERS check performed here, but TLS/crypto code in this repo typically has designated owners.

@robobun

robobun commented May 26, 2026

Copy link
Copy Markdown
Collaborator Author

CI status

The diff is green on the TLS lanes: node-tls-set-default-ca-certificates.test.ts (12 subprocess tests), ssl-ctx-cache.test.ts, and all seven upstream test-tls-set-default-ca-certificates-*.js pass on every platform including linux-x64-asan, darwin, Windows, and musl.

Build 63126 (cb396e9) failed on lanes unrelated to this diff:

Failure Lane Relation to this PR
v8-heap-snapshot.test.ts SIGKILL ubuntu 25.04 x64 none; OOM-killed heap snapshot test
bun-install-registry.test.ts hoisting windows aarch64 (flaky, retried) none; package manager
test/package.json install timeout debian x64-asan (flaky, retried) none; install infra

None touch node:tls, openssl.c, root_certs.cpp, or anything in this diff. The single CI re-roll was already used on an earlier build; this needs a maintainer to merge past the flake or re-run the one failed job.

Review status

All CodeRabbit and claude[bot] review threads resolved. The final automated review (on cb396e9) found no new issues and flagged the change as security-sensitive native TLS code that merits a human pass — which is expected for a trust-store change.

tls.setDefaultCACertificates(certs) replaces the default CA trust store
used for TLS client verification when no explicit 'ca' option is given.
After calling it, tls.getCACertificates('default') returns exactly the
supplied (deduplicated) certificates, and new TLS/HTTPS connections
verify against that set instead of the bundled Mozilla roots.

- root_certs.cpp: mutex-guarded process-global user cert override that
  us_get_default_ca_store() honours exclusively when present; the cached
  shared store is invalidated on each setDefaultCACertificates() call.
  has_user_root_certs is atomic for the lock-free fast-path check.
- openssl.c: SSL_CTXs built against the default CA set (request_cert,
  no explicit ca) are tagged via ex_data so us_internal_ssl_attach()
  refreshes the per-SSL verify store after the defaults change — the
  cached fetch/https CTX picks up the override without a rebuild.
- NodeTLS.cpp: resetRootCertStore parses PEM strings/ArrayBufferViews
  (multi-cert bundles supported), deduplicates by X509 identity, and
  installs the set; getUserRootCertificates returns an up-ref'd PEM
  snapshot, or undefined when no override is installed.
- tls.ts: validates input (ERR_INVALID_ARG_TYPE), queries the native
  override state so Workers always report the store their connections
  actually verify against.

Fixes #24340
Fixes #13868
@robobun
robobun force-pushed the farm/06cd3e87/tls-set-default-ca-certificates branch from b54356c to 14bac29 Compare June 17, 2026 11:00
Comment thread packages/bun-usockets/src/crypto/root_certs.cpp
…identity

setDefaultCACertificates() invalidates and rebuilds the cached shared
X509_STORE, so the old pointer-identity check in us_ssl_ctx_add_ca_cert
(store == us_get_shared_default_ca_store()) no longer recognises a
CTX holding the stale shared store, and would mutate it in place,
leaking the added CA into every sibling CTX still referencing it.

Gate the clone on !us_ctx_user_ca_ex_idx instead: that flag is set
exactly when the CTX has a private store, so its absence means the
store is shared-or-empty and must be cloned before appending.

Also mirror the client-side per-SSL verify-store refresh on the
server accept path so an mTLS server built before
setDefaultCACertificates() verifies client certificates against the
current defaults.

@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 didn't find any new issues, but this rewrites the TLS default trust-store as a process-global, mutex-invalidated cache with per-SSL verify-store refresh on both client and server attach paths — security-critical native code that's worth a human pass, especially the intentional process-global (vs Node's per-thread) override semantics and the cb396e9 addCACert clone-on-write change.

Extended reasoning...

Overview

This PR replaces the JS-only tls.setDefaultCACertificates() stub with a native trust-store override. It touches:

  • packages/bun-usockets/src/crypto/root_certs.cpp — moves the cached shared X509_STORE from std::call_once to a mutex-invalidated cache; adds process-global user_root_certs guarded by shared_store_mutex and a relaxed-atomic has_user_root_certs flag.
  • packages/bun-usockets/src/crypto/openssl.c — adds per-SSL verify-store refresh on both the client connect and server accept paths in us_internal_ssl_attach, and rewrites us_ssl_ctx_add_ca_cert's clone-on-write to gate on the us_ctx_user_ca_ex_idx flag instead of pointer identity against the (now mutable) shared store.
  • src/jsc/bindings/NodeTLS.cpp — ~210 lines of new JSC host functions: PEM bundle parsing, X509 dedup via std::set<X509*, X509DerLess>, snapshot-under-lock for getUserRootCertificates.
  • src/js/node/tls.ts — removes the _defaultCACertificatesOverride JS hook from #31155 and routes through native; getCACertificates('default') now queries native on every call.
  • ~480 lines of new tests across two files plus an updated ssl-ctx-cache.test.ts that flips the ca: [] semantics.

Security risks

This is squarely security-sensitive: it governs which CAs every default TLS connection in the process trusts. Specific areas:

  • Trust-store isolation: the cb396e9 fix to us_ssl_ctx_add_ca_cert changes how clone-on-write detects a shared store. The new gate (!us_ctx_user_ca_ex_idx) is correct for stores attached by Bun's own paths, but assumes every code path that hands a shared/aliased store to an SSL_CTX also leaves that flag clear.
  • Server accept path now overrides the verify store per-SSL when us_has_user_root_certs() is true — newly-added behavior that affects mTLS client-cert verification.
  • Process-global override: one Worker calling setDefaultCACertificates([]) empties the trust store for the entire process. This is an intentional, documented divergence from Node's per-thread semantics, but it's a design call a maintainer should sign off on.
  • The review history surfaced and fixed a UAF (unlocked snapshot iteration), a per-VM/process-global desync, an X509_up_ref leak, a bool data race, and a clone-on-write regression — all addressed, but the density of memory-safety/concurrency fixes underscores the complexity.

Level of scrutiny

High. This is native C/C++ in the TLS verification path, with OpenSSL refcounting, cross-thread mutex protocols, and a behavioral change to an existing function (us_ssl_ctx_add_ca_cert). It also flips a test assertion in ssl-ctx-cache.test.ts (ca: [] now authorizes where it previously didn't), which is a user-visible behavior change explained in the PR body as a known follow-up.

Other factors

  • All prior automated review threads (CodeRabbit + my own) are resolved with code changes and explanations.
  • CI is green on the new TLS tests across all lanes including ASAN; the 2 remaining failures are unrelated flakes (terminal.test.ts, next-pages/dev-server.test.ts).
  • All seven upstream Node test-tls-set-default-ca-certificates-*.js tests reportedly pass unmodified.
  • No CODEOWNERS check performed, but packages/bun-usockets/src/crypto/ is core networking infrastructure.

This is well-tested and the iteration history is thorough, but it's not a change I can approve without a human looking at the trust-store semantics and the openssl.c attach-path changes.

@robobun

robobun commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #31155. #24340 is now closed as fixed on main.

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.

Ability to set NODE_EXTRA_CA_CERTS in standalone executable node:tls does not implement setDefaultCACertificates

1 participant