Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
168 commits
Select commit Hold shift + click to select a range
7f82a21
node:worker_threads: add 5 upstream Node worker tests that already pass
cirospaciari Jul 16, 2026
f69da29
node:worker_threads: add 3 more upstream Node worker tests that alrea…
cirospaciari Jul 17, 2026
356f936
node:worker_threads: drop test-worker-dns-terminate, exempt fshandles…
cirospaciari Jul 17, 2026
f57c53b
node:worker_threads: exempt both fshandles tests from LeakSanitizer
cirospaciari Jul 17, 2026
2872f8f
node:worker_threads: drop the two fshandles tests instead of exemptin…
cirospaciari Jul 17, 2026
c8a6e90
worker_threads: publish node's 'worker_threads' diagnostics channel
cirospaciari Jul 17, 2026
10fe341
worker_threads: async_hooks WORKER resource, error-clone stack, share…
cirospaciari Jul 17, 2026
c308af4
BroadcastChannel: ref() should return the channel
cirospaciari Jul 17, 2026
f1384cd
worker_threads: fire 'online' before the entry point runs, as node does
cirospaciari Jul 17, 2026
5056973
Merge origin/main into ciro/worker-threads-node-tests
cirospaciari Jul 17, 2026
d7e1659
worker_threads: address review feedback and drop test-worker-memory
robobun Jul 17, 2026
c7d3e81
cli: support node's -pe alias
cirospaciari Jul 17, 2026
80aae50
worker_threads: profile worker threads under --cpu-prof
cirospaciari Jul 17, 2026
e61bfa4
cli: scope the -pe alias to the bun/node entry points in execArgv
robobun Jul 17, 2026
4f599cf
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
fb8b3d3
cpu profiler: clamp the sampling interval to a usable range
cirospaciari Jul 17, 2026
dbe27a0
tls: make --use-system-ca a per-Environment option, add --no-use-syst…
cirospaciari Jul 17, 2026
0fe453a
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
e20129c
cpu profiler: do not inherit --cpu-prof-name into workers
robobun Jul 17, 2026
3a09c2e
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
fc01f2f
worker_threads: keep error.code when the thrown value cannot be cloned
cirospaciari Jul 17, 2026
57cf856
perf_hooks: implement eventLoopUtilization()
cirospaciari Jul 17, 2026
dfe31cd
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
aba81dd
cpu profiler: let workers inherit --cpu-prof-name, as node does
cirospaciari Jul 17, 2026
74c1ebf
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 17, 2026
f17365c
fix lint and clippy fallout from the eventLoopUtilization work
cirospaciari Jul 17, 2026
997ce14
address review: once() this-binding, init sites, nested cpu-prof, lint
robobun Jul 17, 2026
e19fa93
Merge branch 'main' into ciro/worker-threads-node-tests
cirospaciari Jul 17, 2026
1fbc0d1
eventLoopUtilization: fix two cross-thread races
robobun Jul 18, 2026
19b36ff
eventLoopUtilization: use seq_cst for the idle_ns/idle_entry_ns pair
robobun Jul 18, 2026
c7cfb4e
web_worker: fix two aliasing/race hazards in the cross-thread ELU/cpu…
robobun Jul 18, 2026
f0f5e8e
[autofix.ci] apply automated fixes
autofix-ci[bot] Jul 18, 2026
de70866
ci: retrigger
robobun Jul 18, 2026
31fb9fc
epoll_kqueue: hoist the post-park clock read before zeroing idle_entr…
robobun Jul 18, 2026
a757b03
Worker.cpp: guard errorCodeOf against a pending TerminationException
robobun Jul 18, 2026
46f9f32
Merge branch 'main' into ciro/worker-threads-node-tests
cirospaciari Jul 19, 2026
1efb86f
Merge remote-tracking branch 'origin/main' into ciro/worker-threads-n…
robobun Jul 22, 2026
89e5f6e
Merge remote-tracking branch 'origin/main' into ciro/worker-threads-n…
robobun Aug 3, 2026
747cbbb
trim comments to <=3 lines, cite spec/node source
robobun Aug 3, 2026
79b3862
Worker.cpp: guard WebWorker__dispatchError/dispatchErrorWithValue aga…
robobun Aug 3, 2026
388af0e
bun_get_loop_elu: use raw us_loop_idle_ns, drop the &self idle_ns wra…
robobun Aug 3, 2026
134c6b0
Merge remote-tracking branch 'origin/main' into ciro/worker-threads-n…
robobun Aug 4, 2026
5e6f3ac
Merge branch 'main' into ciro/worker-threads-node-tests
cirospaciari Aug 5, 2026
fb72372
web_worker: don't report a terminate()-rejected entry promise as unca…
robobun Aug 5, 2026
f99b7c0
Add missing SAFETY comment on us_loop_idle_ns call
cirospaciari Aug 5, 2026
20f5734
web_worker: close the Bun-side termination-exception holes in the ter…
robobun Aug 5, 2026
04dca3c
worker: keep flush_logs and teardown termination-safe on the remainin…
cirospaciari Aug 5, 2026
a4993b9
Merge remote-tracking branch 'origin/main' into ciro/worker-threads-n…
robobun Aug 7, 2026
5d77db6
Merge branch 'main' into ciro/worker-threads-node-tests
cirospaciari Aug 7, 2026
3b28311
execArgv: drop the unconditional -pe insertion the merge reintroduced
robobun Aug 7, 2026
49f1a4f
Trim comments to node-source/spec references
robobun Aug 7, 2026
7d70027
Skip worker_destruction under ASAN while the terminate-during-load as…
robobun Aug 7, 2026
c6045a8
worker_destruction: terminate when the worker reports its action started
robobun Aug 8, 2026
3288369
Merge remote-tracking branch 'origin/main' into ciro/worker-threads-n…
dylan-conway Aug 8, 2026
bc4eab3
worker_threads: read a Worker's hasRef / loop utilization through pri…
dylan-conway Aug 8, 2026
f3c28b8
Reattach release_parent_poll_ref doc comment to its function
robobun Aug 8, 2026
8c4567d
Drop unreachable keep-alive guard in WorkerMessagingProxy::hasRef
robobun Aug 8, 2026
94db9ae
worker_threads: honour --cpu-prof-dir, --cpu-prof-name and --cpu-prof…
dylan-conway Aug 8, 2026
fec0b60
perf_hooks: eventLoopUtilization needs no null-loop guard
dylan-conway Aug 8, 2026
47cceef
tls: a Worker's --use-system-ca / --no-use-system-ca governs the root…
dylan-conway Aug 9, 2026
e5e73b0
Merge remote-tracking branch 'origin/main' into ciro/worker-threads-n…
dylan-conway Aug 9, 2026
153e900
Fix two review findings in the per-worker use-system-ca plumbing
robobun Aug 9, 2026
e7f89fd
Only install OpenSSL default cert paths in the system-CA store variant
robobun Aug 9, 2026
ab623d0
Merge branch 'main' into ciro/worker-threads-node-tests
cirospaciari Aug 10, 2026
dc6b8bb
tls.getCACertificates('default') reports the decision connections use
robobun Aug 10, 2026
6222f4d
root_certs: cite node's NewRootCertStore for the default-paths gating
robobun Aug 10, 2026
6ab78d8
Restore per-thread NODE_USE_SYSTEM_CA fallback in getCACertificates
robobun Aug 10, 2026
28ea0a8
worker_threads: apply the thread's --use-system-ca to the default cli…
cirospaciari Aug 10, 2026
bb88250
worker_threads: resolve a flagless thread's CA option from its env, r…
cirospaciari Aug 10, 2026
6fc1c9d
tls/worker_threads: keep OpenSSL's default lookups in the flagless st…
cirospaciari Aug 10, 2026
efc5781
tls: back to node's root-store rules; make --use-openssl-ca exclusive
cirospaciari Aug 10, 2026
8eb5f3e
uws: safety comment on us_get_shared_default_ca_store matches its arg…
robobun Aug 10, 2026
9d5b413
tls.getCACertificates('default') leaves bundled and system roots out …
robobun Aug 10, 2026
ef0dc09
tls/worker_threads: system store is the OS store alone, openssl-ca re…
cirospaciari Aug 10, 2026
b6f0c62
Pin the web Worker 'open' event to thread start, ahead of the entry p…
robobun Aug 10, 2026
a24ad08
web_worker: borrow the ELU atomics directly (clippy deref_addrof)
cirospaciari Aug 11, 2026
3fcfde3
Point the use_system_ca_flag docs at InitOptions; arm the ELU test's …
robobun Aug 11, 2026
da612ae
Merge branch 'main' into ciro/worker-threads-node-tests
alii Aug 11, 2026
09f3e89
bake: init the production VM as the main thread like its siblings
robobun Aug 11, 2026
7e37ca0
bake: pass the CA flag explicitly instead of initializing as the main…
robobun Aug 11, 2026
ca91126
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 16, 2026
136b4a9
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 16, 2026
d86e8fb
Pin the monotonic half of the shared process origin
robobun Aug 16, 2026
b5238ab
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 18, 2026
0ad37c0
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 20, 2026
0efc5c3
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 21, 2026
b268ba7
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 22, 2026
a467125
perf_hooks: do not stamp the loop start while the entry graph is loading
robobun Aug 22, 2026
60faa85
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 22, 2026
1e56750
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 23, 2026
e43d88b
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 23, 2026
410561e
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 24, 2026
2e9b395
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 24, 2026
83cd09a
resolver: keep ?query on file:// URL import specifiers
robobun Aug 17, 2026
b4679db
test: cover import.meta.resolveSync, require and static instances for…
robobun Aug 17, 2026
6f9919a
ci: retrigger
robobun Aug 24, 2026
aa63a92
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 25, 2026
3d3aeda
cli: read --no-use-system-ca once
robobun Aug 25, 2026
85ba575
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 26, 2026
3b0831e
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Aug 27, 2026
9d4fa64
Resolve relative imports of a module imported with a ?query that cont…
robobun Sep 7, 2026
94cb78e
Merge branch 'main' into ciro/worker-threads-node-tests
robobun Sep 8, 2026
1286c72
Strip the ?query from the parent filename in Module._resolveLookupPat…
robobun Sep 8, 2026
e3b88b4
Shorten the referrer-key comment in _resolve
robobun Sep 8, 2026
7306e91
Keep a Windows \\?\ device prefix out of the module-key query split
robobun Sep 8, 2026
eb93d38
[autofix.ci] apply automated fixes
autofix-ci[bot] Sep 8, 2026
7651365
Trim helper doc comments
robobun Sep 8, 2026
837c061
One-line helper docs, name the device-prefix length
robobun Sep 8, 2026
c50f1ae
fix(sqlite): allow workers to reuse custom library
steipete Sep 11, 2026
6592488
fix(sqlite): unload rejected custom libraries
steipete Sep 11, 2026
a12f3a7
fix(node:fs): preserve POSIX locks in realpath
steipete Sep 11, 2026
39b8131
test(node:fs): use static FFI import
steipete Sep 11, 2026
17d1237
fix(node:fs): preserve child rm permission errors
steipete Sep 12, 2026
1c634a4
fix(node:fs): preserve realpath variant semantics
steipete Sep 12, 2026
d0dd32e
Merge remote-tracking branch 'origin/main' into farm/a301c65a/file-ur…
robobun Sep 12, 2026
ba7e56a
resolver: keep #fragment on file:// URL and relative import specifiers
robobun Sep 12, 2026
1b4e157
Merge remote-tracking branch 'origin/main' into farm/a301c65a/file-ur…
robobun Sep 13, 2026
c595e47
fix(node:https): support live secure context updates
steipete Sep 13, 2026
d5335b6
Merge #41929 (robobun/537726df/referrer-query-slash): cut the referre…
robobun Sep 13, 2026
eb8fb40
resolver: keep this change to file:// URL specifiers
robobun Sep 13, 2026
e2ed709
test: do not pipe the unread stdout of the bun build --compile step
robobun Sep 13, 2026
2b87b81
fix(node:https): handle PFX context replacements
steipete Sep 13, 2026
5bb78b2
fix(runtime): preserve encoded file URL path delimiters
steipete Sep 13, 2026
b4095c8
fix(runtime): canonicalize encoded file URL keys
steipete Sep 13, 2026
b1bf47e
fix(node:https): wrap injected raw connections with TLS
steipete Sep 13, 2026
f72285d
fix(worker_threads): preserve async context for worker events
steipete Sep 13, 2026
772e4ac
fix(node:os): observe runtime HOME changes
steipete Sep 13, 2026
7254eae
fix(worker_threads): preserve error metadata
steipete Sep 13, 2026
d13e0f0
fix(node:path): honor replaced process.cwd
steipete Sep 13, 2026
b35488e
fix(process): allow clearing exitCode
steipete Sep 13, 2026
564e853
fix(node:http): uncork reused upgrade sockets
steipete Sep 13, 2026
bbb2dd4
fix(node): resolve listen hosts before binding
steipete Sep 13, 2026
0f92e9e
fix(node:module): synchronize builtin ESM exports
steipete Sep 13, 2026
efb64b4
fix(worker_threads): apply execArgv preloads
steipete Sep 13, 2026
87ef2e6
fix(node:async_hooks): report timer lifecycles
steipete Sep 13, 2026
23e36c8
fix(node:http): align shutdown transport lifecycle
steipete Sep 13, 2026
08da1c8
fix(runtime): derive data URL loaders from MIME
steipete Sep 13, 2026
4df5e06
fix(node:fs): preserve literal POSIX backslashes in realpath
steipete Sep 13, 2026
891eb8d
fix(node:fs): preserve Win32 semantics in recursive mkdir checks
steipete Sep 13, 2026
bacfa9e
test(process): isolate exit-code fixture imports
steipete Sep 13, 2026
d51f9cb
fix(node:http): preserve caller cork ownership
steipete Sep 13, 2026
82a9d26
fix(node:https): handle injected handshake failures
steipete Sep 13, 2026
8117fc9
fix(worker_threads): preserve handled preload exits
steipete Sep 13, 2026
eeee5e1
fix(node:path): isolate cwd callbacks from scratch
steipete Sep 13, 2026
a61f8c9
fix(node:https): preserve default roots for PFX contexts
steipete Sep 13, 2026
8530f60
fix(node): scope listen callbacks to server generation
steipete Sep 13, 2026
aa78523
fix(node:module): make builtin export sync atomic
steipete Sep 13, 2026
6e044db
fix(node:async_hooks): harden destroy scheduling
steipete Sep 13, 2026
cc5b9fb
fix(runtime): preserve CommonJS file URL delimiters
steipete Sep 13, 2026
7f18471
fix(node:http): verify dispatcher cork acquisition
steipete Sep 13, 2026
f1def31
fix(node:http): harden shutdown lifecycle races
steipete Sep 13, 2026
af0f573
Skip the debug source dump for data: URL specifiers
robobun Sep 13, 2026
3764f8b
test(worker_threads): isolate preload failure probes
steipete Sep 13, 2026
bcfa554
fix(bundler): align data URL loader fallback
steipete Sep 13, 2026
4570e10
test(bundler): distinguish external data URL imports
steipete Sep 13, 2026
ba1a004
fix(node:http): preserve overlapping close ownership
steipete Sep 13, 2026
b8666fd
test(node:https): await PFX lifecycle fixture
steipete Sep 13, 2026
5b9ab56
fix(node): preserve listen callback event ownership
steipete Sep 13, 2026
4f214e8
fix(node:http): gate overlapping native close completion
steipete Sep 13, 2026
e040ec4
fix(node:path): preserve cwd surrogates in shortcuts
steipete Sep 13, 2026
e82bdce
fix(worker_threads): preload module eval workers
steipete Sep 13, 2026
747299f
fix(node:http): count pre-handshake connections during close
steipete Sep 13, 2026
98d5f81
fix(node:http): await all overlapping listener generations
steipete Sep 13, 2026
1705753
fix(worker_threads): preserve preload module semantics
steipete Sep 13, 2026
60fbb60
fix(runtime): apply require preload conditions
steipete Sep 13, 2026
33f8935
fix(node:http): retain partial cork ownership
steipete Sep 13, 2026
d781c29
feat(compat): integrate upstream Bun PRs for OpenClaw
steipete Sep 14, 2026
597b78c
fix(compat): resolve integration lint findings
steipete Sep 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# OpenClaw Bun fork changelog

## Unreleased

- Integrate 19 pending upstream PRs for filesystem, SQLite, worker, async-hook, HTTP/TLS, module-loader, process, and path compatibility, retaining their original histories and required worker/query-cache prerequisites.
- Resolve interactions between worker and timer hooks, pending HTTPS listen configuration, forceful shutdown after graceful close, and literal filename delimiters in module resolution and lookup paths.
2 changes: 2 additions & 0 deletions docs/runtime/sqlite.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,8 @@ const db = new Database();
db.loadExtension("myext");
```

Workers can repeat `setCustomSQLite()` with the exact same path. Bun rejects a different path after SQLite loads.

</Note>

### `.fileControl(cmd: number, value: any)`
Expand Down
9 changes: 4 additions & 5 deletions packages/bun-types/sqlite.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,11 +341,10 @@ declare module "bun:sqlite" {
*
* @note macOS-only
*
* This only works before SQLite is loaded, that is,
* before you call `new Database()`.
*
* It can only be run once because it loads
* the SQLite library into the process.
* The initial call only works before SQLite is loaded, that is,
* before you call `new Database()`. Later calls with the exact same path
* are idempotent so workers can adopt the process-wide selection.
* A different path is rejected after SQLite loads.
*
* @param path The path to the SQLite library
*/
Expand Down
41 changes: 33 additions & 8 deletions packages/bun-usockets/src/crypto/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ static int us_ctx_cache_ex_idx = -1;
* ca/caFile options or a later addCACert): the per-socket client attach must
* not replace such a store with the process-shared default roots. */
static int us_ctx_user_ca_ex_idx = -1;
/* The resolved system-CA decision the context was built with (stored as value+1 so 0 = unset). */
static int us_ctx_use_system_ca_ex_idx = -1;
static int us_ssl_reneg_state_idx = -1;
/* Per-connection async-SNI suspension state (select_certificate_cb retry). */
static int us_ssl_sni_pending_idx = -1;
Expand Down Expand Up @@ -450,6 +452,7 @@ static void us_ex_idx_init(void) {
us_sni_ex_idx = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
us_ctx_cache_ex_idx = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, bun_ssl_ctx_cache_on_free);
us_ctx_user_ca_ex_idx = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
us_ctx_use_system_ca_ex_idx = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
us_ctx_sni_policy_ex_idx = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, NULL);
us_ssl_reneg_state_idx = SSL_get_ex_new_index(0, NULL, NULL, NULL, us_ssl_reneg_state_free);
us_ssl_sni_pending_idx = SSL_get_ex_new_index(0, NULL, NULL, NULL, us_ssl_sni_pending_free);
Expand Down Expand Up @@ -961,13 +964,15 @@ static int us_ssl_ctx_use_privatekey_content(SSL_CTX *ctx, const char *content,
* the still-empty SSL_CTX_new() store are first replaced by a private full
* default-root copy, and the context is marked so the per-socket attach keeps
* it. https://github.com/nodejs/node/blob/v26.3.0/src/crypto/crypto_context.cc#L1831 */
int us_ssl_ctx_use_system_ca(SSL_CTX *ctx) {
us_ex_idx_ensure();
intptr_t stored = (intptr_t)SSL_CTX_get_ex_data(ctx, us_ctx_use_system_ca_ex_idx);
return stored ? (int)(stored - 1) : us_default_use_system_ca();
}

static X509_STORE *us_ssl_ctx_get_own_cert_store(SSL_CTX *ctx) {
X509_STORE *store = SSL_CTX_get_cert_store(ctx);
/* us_get_shared_default_ca_store() up-refs before returning, so release
* the reference taken just for this comparison. */
X509_STORE *shared = us_get_shared_default_ca_store();
int store_is_shared = store != NULL && store == shared;
X509_STORE_free(shared);
int store_is_shared = us_is_shared_default_ca_store(store);
us_ex_idx_ensure();
int store_is_empty = 0;
if (store != NULL && !store_is_shared) {
Expand All @@ -979,7 +984,7 @@ static X509_STORE *us_ssl_ctx_get_own_cert_store(SSL_CTX *ctx) {
* no `ca` configured at all may be seeded with the default roots here. */
int user_ca = SSL_CTX_get_ex_data(ctx, us_ctx_user_ca_ex_idx) != NULL;
if (store == NULL || store_is_shared || (store_is_empty && !user_ca)) {
X509_STORE *own = us_get_default_ca_store();
X509_STORE *own = us_get_default_ca_store(us_ssl_ctx_use_system_ca(ctx));
if (own == NULL) {
return NULL;
}
Expand Down Expand Up @@ -1269,6 +1274,9 @@ SSL_CTX *us_ssl_ctx_build_raw(struct us_bun_socket_context_options_t options,
/* Register the live-count free_func first thing so every exit (including
* build_fail) balances. The packed reneg policy reuses the same slot. */
SSL_CTX_set_ex_data(ssl_context, us_ssl_ctx_ex_idx(), NULL);
const int use_system_ca = us_resolve_use_system_ca(options.use_system_ca);
us_ex_idx_ensure();
SSL_CTX_set_ex_data(ssl_context, us_ctx_use_system_ca_ex_idx, (void *)(intptr_t)(use_system_ca + 1));

/* Default options we rely on — changing these breaks the BIO logic. */
SSL_CTX_set_read_ahead(ssl_context, 1);
Expand Down Expand Up @@ -1401,7 +1409,7 @@ SSL_CTX *us_ssl_ctx_build_raw(struct us_bun_socket_context_options_t options,
* addRootCerts() when `ca` is absent - the handshake-time auto-chain and
* (for requestCert) client verification both read it. The getter up-refs,
* so set_cert_store owns exactly one reference per context. */
SSL_CTX_set_cert_store(ssl_context, us_get_shared_default_ca_store());
SSL_CTX_set_cert_store(ssl_context, us_get_shared_default_ca_store(use_system_ca));
if (options.request_cert) {
SSL_CTX_set_verify(ssl_context,
options.reject_unauthorized ? (SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT)
Expand Down Expand Up @@ -1792,7 +1800,7 @@ void us_internal_ssl_attach(struct us_socket_t *s, SSL_CTX *ctx,
* A context whose store holds user-provided CAs (ca/caFile options or
* addCACert) keeps using its own store - overriding it here would
* hide those CAs from chain verification. */
X509_STORE *roots = us_get_shared_default_ca_store();
X509_STORE *roots = us_get_shared_default_ca_store(us_ssl_ctx_use_system_ca(ctx));
if (roots) SSL_set0_verify_cert_store(ssl, roots);
}
}
Expand Down Expand Up @@ -3277,6 +3285,23 @@ static int sni_cb(SSL *ssl, int *al, void *arg) {
return SSL_TLSEXT_ERR_OK;
}

void us_listen_socket_set_ssl_ctx(struct us_listen_socket_t *ls, SSL_CTX *ctx) {
if (ls->ssl_ctx == ctx) return;

SSL_CTX_up_ref(ctx);
SSL_CTX *previous = ls->ssl_ctx;
ls->ssl_ctx = ctx;

if (ls->sni) {
SSL_CTX_set_tlsext_servername_callback(ctx, sni_cb);
}
if (ls->on_server_name) {
SSL_CTX_set_select_certificate_cb(ctx, us_select_cert_cb);
}

if (previous) SSL_CTX_free(previous);
}

int us_listen_socket_add_server_name(struct us_listen_socket_t *ls,
const char *hostname_pattern,
SSL_CTX *ctx, void *user) {
Expand Down
120 changes: 77 additions & 43 deletions packages/bun-usockets/src/crypto/root_certs.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "./root_certs_header.h"
#include "./internal/internal.h"
#include <mutex>
#include <atomic>
#include <string.h>
#include <string_view>
#include <unordered_set>
Expand Down Expand Up @@ -48,22 +49,34 @@ static void us_cert_file_into_bio(void *ctx, const uint8_t *data, size_t len) {
// Forward declarations for platform-specific functions
// (Actual implementations are in platform-specific files)

// External variable from Zig CLI arguments
// External variables from the CLI arguments
extern "C" bool Bun__Node__UseSystemCA;

// Helper function to check if system CA should be used
// Checks both CLI flag (--use-system-ca) and environment variable (NODE_USE_SYSTEM_CA=1)
static bool us_should_use_system_ca() {
// Check CLI flag first
extern "C" bool Bun__Node__NoUseSystemCA;
// BunCAStore discriminant (Arguments.rs): 1 == --use-openssl-ca.
extern "C" uint8_t Bun__Node__CAStore;
static const uint8_t BUN_CA_STORE_OPENSSL = 1;

// The process-wide default: --no-use-system-ca beats everything, then --use-system-ca, then
// NODE_USE_SYSTEM_CA=1. A thread (node: Environment) started with its own flag overrides this for
// the contexts it creates — see us_bun_socket_context_options_t.use_system_ca.
extern "C" int us_default_use_system_ca() {
if (Bun__Node__NoUseSystemCA) {
return 0;
}
if (Bun__Node__UseSystemCA) {
return true;
return 1;
}

// Check environment variable
const char *use_system_ca = getenv("NODE_USE_SYSTEM_CA");
return use_system_ca && strcmp(use_system_ca, "1") == 0;
}

// Resolve an options-struct tri-state (0: process default, >0: include system roots, <0: exclude).
extern "C" int us_resolve_use_system_ca(int requested) {
if (requested > 0) return 1;
if (requested < 0) return 0;
return us_default_use_system_ca();
}

// Platform-specific system certificate loading implementations are separated:
// - macOS: root_certs_darwin.cpp (Security framework with dynamic loading)
// - Windows: root_certs_windows.cpp (Windows CryptoAPI)
Expand Down Expand Up @@ -291,39 +304,49 @@ const us_system_certs_t &us_get_root_system_certs() {
return system_certs;
}

extern "C" X509_STORE *us_get_default_ca_store() {
extern "C" X509_STORE *us_get_default_ca_store(int use_system_ca) {
X509_STORE *store = X509_STORE_new();
if (store == NULL) {
return NULL;
}
X509_STORE_set_flags(store, X509_V_FLAG_IGNORE_EXPIRED_TRUST_ANCHORS);

X509_LAZY_CERT_SET *bundled = us_get_bundled_root_cert_set();
if (bundled == NULL || !X509_STORE_add_lazy_cert_set(store, bundled)) {
X509_STORE_free(store);
return NULL;
}
// --use-openssl-ca: OpenSSL's default lookups *instead of* the bundled roots, and no system store, as in node's
// NewRootCertStore (https://github.com/nodejs/node/blob/v26.3.0/src/crypto/crypto_context.cc#L1099-L1109).
const int openssl_ca = Bun__Node__CAStore == BUN_CA_STORE_OPENSSL;
if (openssl_ca) {
if (!X509_STORE_set_default_paths(store)) {
X509_STORE_free(store);
return NULL;
}
} else {
X509_LAZY_CERT_SET *bundled = us_get_bundled_root_cert_set();
if (bundled == NULL || !X509_STORE_add_lazy_cert_set(store, bundled)) {
X509_STORE_free(store);
return NULL;
}

// What X509_STORE_set_default_paths(store) trusts: the default certificate file (above) and the default hashed
// certificate directory, which BoringSSL already consults lazily per lookup.
const us_openssl_default_cert_file &file = us_get_openssl_default_cert_file();
if (file.certs != nullptr && !X509_STORE_add_lazy_cert_set(store, file.certs)) {
X509_STORE_free(store);
return NULL;
}
for (size_t i = 0; file.trusted != nullptr && i < sk_X509_num(file.trusted); i++) {
X509_STORE_add_cert(store, sk_X509_value(file.trusted, i));
}
for (size_t i = 0; file.crls != nullptr && i < sk_X509_CRL_num(file.crls); i++) {
X509_STORE_add_crl(store, sk_X509_CRL_value(file.crls, i));
}
X509_LOOKUP *hash_dir = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
if (hash_dir == NULL) {
X509_STORE_free(store);
return NULL;
// What X509_STORE_set_default_paths(store) trusts: the default certificate file (above) and the default hashed
// certificate directory, which BoringSSL already consults lazily per lookup.
const us_openssl_default_cert_file &file = us_get_openssl_default_cert_file();
if (file.certs != nullptr && !X509_STORE_add_lazy_cert_set(store, file.certs)) {
X509_STORE_free(store);
return NULL;
}
for (size_t i = 0; file.trusted != nullptr && i < sk_X509_num(file.trusted); i++) {
X509_STORE_add_cert(store, sk_X509_value(file.trusted, i));
}
for (size_t i = 0; file.crls != nullptr && i < sk_X509_CRL_num(file.crls); i++) {
X509_STORE_add_crl(store, sk_X509_CRL_value(file.crls, i));
}
X509_LOOKUP *hash_dir = X509_STORE_add_lookup(store, X509_LOOKUP_hash_dir());
if (hash_dir == NULL) {
X509_STORE_free(store);
return NULL;
}
X509_LOOKUP_add_dir(hash_dir, NULL, X509_FILETYPE_DEFAULT);
ERR_clear_error();
}
X509_LOOKUP_add_dir(hash_dir, NULL, X509_FILETYPE_DEFAULT);
ERR_clear_error();

STACK_OF(X509) *root_extra_cert_instances = us_get_root_extra_cert_instances();
if (root_extra_cert_instances) {
Expand All @@ -332,7 +355,9 @@ extern "C" X509_STORE *us_get_default_ca_store() {
}
}

if (us_should_use_system_ca()) {
// `use_system_ca` is the decision of the thread this store is built for (us_resolve_use_system_ca), not only the
// process-wide flag: node makes --use-system-ca a per-Environment option.
if (use_system_ca && !openssl_ca) {
const us_system_certs_t &system = us_get_root_system_certs();
if (system.lazy != nullptr && !X509_STORE_add_lazy_cert_set(store, system.lazy)) {
X509_STORE_free(store);
Expand All @@ -346,18 +371,27 @@ extern "C" X509_STORE *us_get_default_ca_store() {
return store;
}

// Process-wide immutable default store. Safe to share across SSL_CTXs that
// don't add per-config CAs (the user-`ca` path in build_raw populates the
// SSL_CTX's own private, initially-empty store instead), so roots parsed for
// one connection's chain are already there for the next.
extern "C" X509_STORE *us_get_shared_default_ca_store() {
static X509_STORE *shared = nullptr;
static std::once_flag once;
std::call_once(once, []() { shared = us_get_default_ca_store(); });
// Process-wide immutable default stores, one per system-CA decision. Safe to share across SSL_CTXs that don't add
// per-config CAs (the user-`ca` path in build_raw populates the SSL_CTX's own private, initially-empty store instead),
// so roots parsed for one connection's chain are already there for the next.
static std::atomic<X509_STORE *> shared_default_ca_store[2] = { nullptr, nullptr };

extern "C" X509_STORE *us_get_shared_default_ca_store(int use_system_ca) {
static std::once_flag once[2];
int i = use_system_ca ? 1 : 0;
std::call_once(once[i], [i]() { shared_default_ca_store[i].store(us_get_default_ca_store(i)); });
X509_STORE *shared = shared_default_ca_store[i].load();
if (shared) X509_STORE_up_ref(shared);
return shared;
}

// Whether `store` is one of the process-shared default stores (as opposed to a context's own).
// Compares against whatever has been built so far; builds nothing.
extern "C" int us_is_shared_default_ca_store(X509_STORE *store) {
return store != nullptr
&& (store == shared_default_ca_store[0].load() || store == shared_default_ca_store[1].load());
}

extern "C" const char *us_get_default_ciphers() {
return DEFAULT_CIPHER_LIST;
}
Expand Down
9 changes: 7 additions & 2 deletions packages/bun-usockets/src/crypto/root_certs_header.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,10 @@ const us_system_certs_t &us_get_root_system_certs();
#define CPPDECL extern
#endif

CPPDECL X509_STORE *us_get_default_ca_store();
CPPDECL X509_STORE *us_get_shared_default_ca_store();
CPPDECL int us_default_use_system_ca();
CPPDECL int us_resolve_use_system_ca(int requested);
CPPDECL X509_STORE *us_get_default_ca_store(int use_system_ca);
CPPDECL X509_STORE *us_get_shared_default_ca_store(int use_system_ca);
CPPDECL int us_is_shared_default_ca_store(X509_STORE *store);
/* The resolved system-CA decision an SSL_CTX built by us_ssl_ctx_build_raw was created with. */
CPPDECL int us_ssl_ctx_use_system_ca(SSL_CTX *ctx);
16 changes: 16 additions & 0 deletions packages/bun-usockets/src/eventing/epoll_kqueue.c
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,10 @@ void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout
}
}

const uint64_t idle_start_ns = will_idle_inside_event_loop ? us_internal_monotonic_ns() : 0;
if (will_idle_inside_event_loop)
__atomic_store_n(&loop->data.idle_entry_ns, idle_start_ns, __ATOMIC_SEQ_CST);

/* Fetch ready polls */
#ifdef LIBUS_USE_EPOLL
/* A zero timespec already has a fast path in ep_poll (fs/eventpoll.c):
Expand All @@ -541,6 +545,18 @@ void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout
timeout);
#endif

if (will_idle_inside_event_loop) {
/* us_loop_idle_ns (another thread) retries while idle_seq is odd or changed underneath it, so
* it never observes the entry cleared without the park added (a non-monotonic sample). */
__atomic_add_fetch(&loop->data.idle_seq, 1, __ATOMIC_SEQ_CST);
/* Clock read inside the odd window: a reader's own clock read (taken before it validated an
* even seq) is then never later than the park length we record, so samples stay monotonic. */
uint64_t now = us_internal_monotonic_ns();
__atomic_store_n(&loop->data.idle_entry_ns, 0, __ATOMIC_SEQ_CST);
__atomic_add_fetch(&loop->data.idle_ns, now - idle_start_ns, __ATOMIC_SEQ_CST);
__atomic_add_fetch(&loop->data.idle_seq, 1, __ATOMIC_SEQ_CST);
}

/* Before anything can allocate again. */
if (handed_off)
mi_on_thread_idle_end();
Expand Down
1 change: 1 addition & 0 deletions packages/bun-usockets/src/eventing/libuv.c
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ struct us_loop_t *us_create_loop(void *hint,
return NULL;
}
loop->is_default = hint != 0;
uv_loop_configure(loop->uv_loop, UV_METRICS_IDLE_TIME);

loop->uv_pre = us_malloc(sizeof(uv_prepare_t));
uv_prepare_init(loop->uv_loop, loop->uv_pre);
Expand Down
3 changes: 3 additions & 0 deletions packages/bun-usockets/src/internal/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ uint64_t us_internal_monotonic_ns(void);
long long us_internal_sweep_timeout_ns(struct us_loop_t *loop);
void us_internal_sweep_if_due(struct us_loop_t *loop);
#endif
/* Nanoseconds this loop has spent parked, including a park in progress. Safe
* from another thread. Both platforms: Rust calls it ungated. */
uint64_t us_loop_idle_ns(struct us_loop_t *loop);
void us_internal_free_closed_sockets(us_loop_r loop);
void us_internal_loop_link_group(struct us_loop_t *loop, struct us_socket_group_t *group);
void us_internal_loop_unlink_group(struct us_loop_t *loop, struct us_socket_group_t *group);
Expand Down
9 changes: 9 additions & 0 deletions packages/bun-usockets/src/internal/loop_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ struct us_internal_loop_data_t {
* for lsquic's time-driven state. POSIX folds the deadline into the
* epoll_pwait2 timeout via getTimeout() instead. */
struct us_timer_t *quic_timer;
#endif
#ifndef LIBUS_USE_LIBUV
/* Nanoseconds parked, for eventLoopUtilization(). Read cross-thread —
* __atomic_* only. MIRRORED in src/uws_sys/InternalLoopData.rs: this struct
* is us_loop_t's first member, so a field here shifts num_polls. */
unsigned long long idle_ns;
unsigned long long idle_entry_ns;
/* Seqlock over the park-exit update of the two fields above (odd while in progress). */
unsigned long long idle_seq;
#endif
struct us_socket_group_t *iterator;
char *recv_buf;
Expand Down
Loading
Loading