Skip to content

Implement NODE_USE_SYSTEM_CA with --use-system-ca CLI flag #22441

Merged
Jarred-Sumner merged 32 commits into
mainfrom
ciro/claude-system-ca
Sep 25, 2025
Merged

Implement NODE_USE_SYSTEM_CA with --use-system-ca CLI flag #22441
Jarred-Sumner merged 32 commits into
mainfrom
ciro/claude-system-ca

Conversation

@cirospaciari

@cirospaciari cirospaciari commented Sep 6, 2025

Copy link
Copy Markdown
Member

What does this PR do?

Resume work on #21898

How did you verify your code works?

Manually tested on MacOS, Windows 11 and Ubuntu 25.04. CI changes are needed for the tests

@robobun

robobun commented Sep 6, 2025

Copy link
Copy Markdown
Collaborator
Updated 9:03 PM PT - Sep 24th, 2025

@Jarred-Sumner, your commit 5f959c2 has some failures in Build #26927 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 22441

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

bun-22441 --bun

Claude Bot and others added 22 commits September 22, 2025 14:04
…k loading

- Add support for NODE_USE_SYSTEM_CA environment variable
- Implement platform-specific system certificate loading:
  * macOS: Dynamic Security framework loading with keychain certificate enumeration
  * Windows: Windows Certificate Store integration
  * Linux/Unix: System certificate file and directory loading
- Add comprehensive trust validation for macOS certificates
- Ensure bundled certificates are used when NODE_USE_SYSTEM_CA is not set
- Add extensive test coverage for all platforms and scenarios
- Follow Node.js behavior and API compatibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
… code

- Extract macOS Security framework code to root_certs_darwin.cpp (333 lines)
- Create root_certs_platform.h header for platform function declarations
- Clean up main root_certs.cpp by removing 300+ lines of macOS-specific code
- Improve code organization and maintainability
- No functional changes - all tests still pass
- Better separation of concerns: each platform has its own file

Files:
- root_certs.cpp: Main certificate loading logic (337 lines, was 600+)
- root_certs_darwin.cpp: macOS Security framework implementation (333 lines)
- root_certs_platform.h: Platform function declarations (17 lines)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add --use-system-ca CLI flag to Arguments.zig
- Integrate CLI flag with existing NODE_USE_SYSTEM_CA environment variable
- CLI flag takes precedence over environment variable
- Update certificate loading logic to check both CLI flag and env var
- Add comprehensive test suite for CLI flag functionality
- All tests pass: CLI flag overrides env var, works independently, TLS support

Usage:
  bun --use-system-ca my-script.js
  NODE_USE_SYSTEM_CA=1 bun my-script.js  (still works)
  bun --use-system-ca my-script.js  (overrides NODE_USE_SYSTEM_CA=0)

Node.js compatibility: Matches Node.js --use-system-ca behavior exactly

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Delete old test that didn't verify certificate loading
- Add test-use-system-ca.test.ts: Tests CLI flag and env var behavior
- Add test-system-ca-https.test.ts: Tests HTTPS with real endpoints
- Add test-node-extra-ca-certs.test.ts: Tests NODE_EXTRA_CA_CERTS

Tests now properly verify:
- --use-system-ca flag loads system certificates
- NODE_USE_SYSTEM_CA=1 environment variable works
- CLI flag overrides environment variable
- HTTPS connections work with system certificates
- NODE_EXTRA_CA_CERTS loads additional certificates

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy Node.js's system certificate tests:
- test-tls-get-ca-certificates-node-use-system-ca.js
- test-openssl-ca-options.js
- test-tls-get-ca-certificates-system.js
- test-native-root-certs.mjs

These are direct copies from Node.js test suite. They will need
tls.getCACertificates() to be implemented to run properly.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Tests verify:
- --use-system-ca CLI flag is accepted
- NODE_USE_SYSTEM_CA=1 environment variable works
- HTTPS requests work with system certificates enabled

All tests pass.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add getSystemCACertificates C++ function to expose system certificates
- Add us_get_root_system_cert_instances() to retrieve cached system certs
- Implement cacheSystemCACertificates() in tls.ts
- Add getUseSystemCA() Zig function to check --use-system-ca flag
- Update cacheDefaultCACertificates() to include system certs when flag is set
- Export getCACertificates() function with support for 'default', 'bundled', 'system', and 'extra' types

The API now matches Node.js behavior:
- getCACertificates('bundled') returns bundled root certificates
- getCACertificates('system') returns system certificates when available
- getCACertificates('extra') returns NODE_EXTRA_CA_CERTS certificates
- getCACertificates('default') returns combined set based on flags

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Fix lazy loading of system certificates when getCACertificates('system') is called
- Fix Security framework dynamic loading by properly checking symbol resolution
- System certificates now load correctly both on-demand and when --use-system-ca flag is set

The implementation now correctly:
- Loads 4 system certificates on macOS (actual count varies by system)
- Includes them in default set when --use-system-ca is used (150 total = 146 bundled + 4 system)
- Works with both CLI flag and NODE_USE_SYSTEM_CA environment variable

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Darwin: Fix SecPolicyCreateSSL to use server=true for proper validation
- Darwin: Add kSecTrustSettingsDomainSystem to trust evaluation loop
- Darwin: Add kSecMatchTrustedOnly to security query for better performance
- Darwin: Restrict certificates to CA certs only using X509_check_ca
- Linux: Add sk_X509_push error checking in directory and bundle loaders
- Linux: Fix SSL_CERT env var semantics to be strict (no fallback if set)
- Linux: Add colon-separated SSL_CERT_DIR path support
- Windows: Restrict cert stores to ROOT only (remove CA and TrustedPeople)
- Fix NODE_USE_SYSTEM_CA to only accept "1" not "true" for Node.js compatibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Fix us_get_root_system_cert_instances() to check flag and avoid race conditions
- Guard system CA additions in us_get_default_ca_store() behind flag check
- Prevents system certs from being loaded when NODE_USE_SYSTEM_CA is disabled

These fixes ensure that system certificates are only loaded when explicitly
enabled and avoid race conditions during initialization.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add BunCAStore enum with bundled/openssl/system options
- Add proper mutual exclusivity validation for CA flags
- Implement CLI > env var precedence for NODE_USE_SYSTEM_CA
- Keep backward compatibility with boolean flag during transition

This addresses CodeRabbit comments about missing mutual exclusivity
and proper precedence handling for CA store selection.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@cirospaciari cirospaciari marked this pull request as ready for review September 22, 2025 22:39
@coderabbitai

coderabbitai Bot commented Sep 22, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds system CA support across platforms. Introduces CLI/env controls, platform-specific loaders (macOS/Linux/Windows), and wiring into TLS APIs. Exposes accessors for system CAs, updates JS/CPP bindings, and adds tests. On Windows, updates linker settings for crypt32. Implements caching, conditional default path loading, and Node-compatible behaviors.

Changes

Cohort / File(s) Summary of changes
Build configuration (Windows)
cmake/targets/BuildBun.cmake
Adds delayed load and link dependency for CRYPT32.dll/crypt32 on WIN32 builds.
Core CA plumbing (uSockets)
packages/bun-usockets/src/crypto/root_certs.cpp, packages/bun-usockets/src/crypto/root_certs_header.h, packages/bun-usockets/src/crypto/root_certs_platform.h
Adds system CA flow: flags/env check, new root_system_cert_instances storage, conditional store population, new accessor, and platform loader declarations.
Platform loaders: macOS
packages/bun-usockets/src/crypto/root_certs_darwin.cpp
Dynamically loads Security/CoreFoundation, extracts trusted CA certs into X509 stack; exposes us_load_system_certificates_macos and cleanup.
Platform loaders: Linux
packages/bun-usockets/src/crypto/root_certs_linux.cpp
Loads system CAs from SSL_CERT_FILE/SSL_CERT_DIR or common paths; supports bundle and directory scanning; handles NODE_EXTRA_CA_CERTS with warning hook.
Platform loaders: Windows
packages/bun-usockets/src/crypto/root_certs_windows.cpp
Enumerates ROOT stores (user and machine) to collect DER certs via CryptoAPI; exposes raw-loading entrypoint.
NodeTLS bindings (C++): accessors
src/bun.js/bindings/NodeTLS.cpp, src/bun.js/bindings/NodeTLS.h
Adds getSystemCACertificates host function; refactors array construction using MarkedArgumentBuffer with overflow checks; includes OpenSSL X509 header.
Runtime flags and API (Zig)
src/cli/Arguments.zig, src/bun.zig
Adds CA store selection flags: --use-system-ca/--use-openssl-ca/--use-bundled-ca; exports Bun__Node__CAStore and Bun__Node__UseSystemCA; exposes JS API getUseSystemCA().
JS integration (Node TLS)
src/js/node/tls.ts
Wires system CA retrieval and caching; aggregates default CA set conditionally; integrates env/flag checks.
Tests: Bun fetch/system CA
test/js/bun/fetch/node-use-system-ca-complete.test.ts, test/js/bun/fetch/node-use-system-ca.test.ts
Adds end-to-end tests for NODE_USE_SYSTEM_CA, flag behavior, HTTPS fetch, and platform cases.
Tests: Node TLS system CA
test/js/node/test/parallel/test-tls-get-ca-certificates-node-use-system-ca.js, test/js/node/test/parallel/test-tls-get-ca-certificates-system.js
Verifies getCACertificates behavior with system/default sets, env vs flag parity, caching, and platform-specific assertions.
Tests: Node TLS extras and HTTPS
test/js/node/tls/test-node-extra-ca-certs.test.ts, test/js/node/tls/test-system-ca-https.test.ts, test/js/node/tls/test-use-system-ca.test.ts
Adds tests for NODE_EXTRA_CA_CERTS handling, HTTPS with system CAs, and precedence/behavior of flags vs env.
Test config
test/no-validate-exceptions.txt
Adds a system CA test path to no-validate exceptions list.

Suggested reviewers

  • markovejnovic

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning While the description includes the required template headings, it does not explain what functionality this PR actually implements or summarize the major file and API changes, relying instead on a reference to a prior PR; this leaves reviewers without context on the new flags, environment variable behavior, certificate loading implementations, and tests added. Expand the “What does this PR do?” section to include a concise summary of the added CLI flags and environment variable logic, the cross-platform system CA loading implementations, changes to public APIs, and the new tests introduced to validate the feature.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly and concisely summarizes the primary change of the pull request, which is implementing the NODE_USE_SYSTEM_CA functionality along with a corresponding --use-system-ca CLI flag, and it accurately reflects the main focus of the extensive cross-platform certificate loading and flag logic added in the diff.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ciro/claude-system-ca

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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: 9

Caution

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

⚠️ Outside diff range comments (2)
packages/bun-usockets/src/crypto/root_certs_header.h (1)

4-15: Fix inconsistent C/C++ linkage for declarations

The first two functions are declared inside the #ifdef __cplusplus block without extern "C", while the store getter uses CPPDECL. Unify to avoid ABI/name‑mangling issues and add (void) for C prototypes.

-#ifdef __cplusplus
-#define CPPDECL extern "C"
-
-STACK_OF(X509) *us_get_root_extra_cert_instances();
-STACK_OF(X509) *us_get_root_system_cert_instances();
-
-#else
-#define CPPDECL extern
-#endif
-
-CPPDECL X509_STORE *us_get_default_ca_store();
+#ifdef __cplusplus
+#define CPPDECL extern "C"
+#else
+#define CPPDECL extern
+#endif
+
+// C/C++ linkage for all declarations
+CPPDECL STACK_OF(X509) *us_get_root_extra_cert_instances(void);
+CPPDECL STACK_OF(X509) *us_get_root_system_cert_instances(void);
+CPPDECL X509_STORE *us_get_default_ca_store(void);
src/bun.js/bindings/NodeTLS.cpp (1)

1-6: Add root.h include to satisfy bindings lints.

Bindings .cpp files should include root.h first.

+#include "root.h"
 #include "config.h"
 
 #include "JavaScriptCore/JSObject.h"
🧹 Nitpick comments (33)
test/no-validate-exceptions.txt (1)

124-124: Trim trailing whitespace on new entry

There’s a trailing space after the filename; it can break exact path matching in filters.

-test/js/node/test/system-ca/test-native-root-certs.test.mjs 
+test/js/node/test/system-ca/test-native-root-certs.test.mjs
test/js/bun/fetch/node-use-system-ca.test.ts (1)

79-134: Low‑value env parsing test

This only asserts env passthrough, not runtime behavior. Consider dropping it or replacing with an assertion on an observable TLS-side effect (e.g., a helper that reports whether system CAs are active).

test/js/node/tls/test-use-system-ca.test.ts (1)

6-20: Test name overpromises vs. assertion

The test asserts process runs and prints “OK” but doesn’t verify that system CAs were actually loaded. Either rename to reflect behavior or add an observable check.

src/bun.zig (1)

3785-3791: Use bun.JSError alias for consistency in JS-exposed Zig APIs

Prefer bun.JSError!jsc.JSValue to match our convention for JS bindings and error propagation.

Apply this diff:

-pub fn getUseSystemCA(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) error{ JSError, OutOfMemory }!jsc.JSValue {
+pub fn getUseSystemCA(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!jsc.JSValue {
test/js/node/test/parallel/test-tls-get-ca-certificates-node-use-system-ca.js (1)

24-29: Avoid code injection; pass expected value as env or parse inside child

Safer to compute inside child rather than interpolating raw text into code.

Apply this diff:

-spawnSyncAndExitWithoutError(process.execPath, [
-  '-p',
-  `assert.strictEqual(tls.getCACertificates('default').length, ${expectedLength.toString()})`,
-], {
-  env: { ...process.env, NODE_USE_SYSTEM_CA: '1' },
-});
+spawnSyncAndExitWithoutError(process.execPath, [
+  '-p',
+  `assert.strictEqual(tls.getCACertificates('default').length, Number(process.env.EXPECTED_LEN))`,
+], {
+  env: { ...process.env, NODE_USE_SYSTEM_CA: '1', EXPECTED_LEN: String(expectedLength) },
+});
src/js/node/tls.ts (1)

946-953: Include system CAs only once; freeze resulting array for immutability

Current logic is fine, but freezing systemCACertificates mirrors how we freeze defaults and prevents accidental mutation.

Apply this diff:

   if (getUseSystemCA() || process.env.NODE_USE_SYSTEM_CA === "1") {
-    const system = cacheSystemCACertificates();
+    const system = cacheSystemCACertificates();
     for (let i = 0; i < system.length; ++i) {
       ArrayPrototypePush.$call(defaultCACertificates, system[i]);
     }
   }

And below (after cacheSystemCACertificates definition):

-let systemCACertificates: string[] | undefined;
+let systemCACertificates: string[] | undefined;
 function cacheSystemCACertificates(): string[] {
-  systemCACertificates ||= getSystemCACertificates() as string[];
+  systemCACertificates ||= ObjectFreeze(getSystemCACertificates() as string[]);
   return systemCACertificates;
 }
test/js/node/tls/test-node-extra-ca-certs.test.ts (2)

41-64: “Missing file” case isn’t validated either

Also query tls.getCACertificates('extra') to ensure graceful handling when the file is absent.

Apply this diff:

-    const dir = tempDirWithFiles("test-missing-ca", {
-      "test.js": `console.log('OK');`,
-    });
+    const dir = tempDirWithFiles("test-missing-ca", {
+      "test.js": `
+        const tls = require('tls');
+        console.log(tls.getCACertificates('extra').length >= 0 ? 'OK' : 'NO');
+      `,
+    });
@@
-    // Process should still run successfully even with missing cert file
     expect(exitCode).toBe(0);
     expect(stdout.trim()).toBe("OK");

66-93: Also exercise combined path with --use-system-ca

Ensure TLS paths execute by querying CAs.

Apply this diff:

-    const dir = tempDirWithFiles("test-extra-and-system", {
-      "extra-ca.pem": testCert,
-      "test.js": `console.log('OK');`,
-    });
+    const dir = tempDirWithFiles("test-extra-and-system", {
+      "extra-ca.pem": testCert,
+      "test.js": `
+        const tls = require('tls');
+        const def = tls.getCACertificates('default').length;
+        const sys = tls.getCACertificates('system').length;
+        const extra = tls.getCACertificates('extra').length;
+        console.log(def >= sys ? 'OK' : 'BAD', def, sys, extra);
+      `,
+    });
@@
-    expect(exitCode).toBe(0);
-    expect(stdout.trim()).toBe("OK");
+    expect(exitCode).toBe(0);
+    expect(stdout.split(/\s+/)[0].trim()).toBe("OK");
test/js/node/test/parallel/test-tls-get-ca-certificates-system.js (1)

23-31: Avoid Set.prototype.intersection for broader engine compatibility

Some JSC versions don’t support Set.prototype.intersection. Use a manual subset check instead.

Apply this diff:

-const defaultSet = new Set(defaultCerts);
-const systemSet = new Set(systemCerts);
-assert.deepStrictEqual(defaultSet.intersection(systemSet), systemSet);
+const defaultSet = new Set(defaultCerts);
+for (const cert of systemCerts) {
+  assert(defaultSet.has(cert));
+}
src/cli/Arguments.zig (3)

768-785: Honor “truthy” env values; reuse getenvTruthy helper

Accept both "1" and "true" (case-insensitive) to match other flags. This also simplifies parsing.

Apply this diff:

-        } else {
-            if (bun.getenvZ("NODE_USE_SYSTEM_CA")) |val| {
-                if (val.len > 0 and val[0] == '1') {
-                    Bun__Node__CAStore = .system;
-                }
-            }
-        }
+        } else if (bun.getenvTruthy("NODE_USE_SYSTEM_CA")) {
+            Bun__Node__CAStore = .system;
+        }

1290-1293: Public exports are fine; consider brief doc comments for embedders

Small nit: adding a line comment describing intended consumers (C++ TLS, JS bindings) helps future maintainers.


759-766: Error message copy tweak (optional)

“choose exactly one” can be misleading when zero are chosen (which is valid). Consider: “at most one of … may be specified”.

Apply this diff:

-            Output.prettyErrorln("<r><red>error<r>: choose exactly one of --use-system-ca, --use-openssl-ca, or --use-bundled-ca", .{});
+            Output.prettyErrorln("<r><red>error<r>: at most one of --use-system-ca, --use-openssl-ca, or --use-bundled-ca may be specified", .{});
packages/bun-usockets/src/crypto/root_certs_windows.cpp (1)

1-7: Trim Windows headers footprint

Define WIN32_LEAN_AND_MEAN and NOMINMAX before including <windows.h> to reduce macro collisions and compile overhead.

 #ifdef _WIN32
 
-#include <windows.h>
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+#include <windows.h>
 #include <wincrypt.h>
 #include <vector>
 #include <cstring>
test/js/bun/fetch/node-use-system-ca-complete.test.ts (3)

60-103: Environment “parsing” test doesn’t validate feature behavior; remove or assert effect

This only echoes env values and never exercises the system-CA code path. Either drop it or rework to assert that enabling/disabling changes trust behavior.

-  test("should properly parse NODE_USE_SYSTEM_CA environment variable", async () => {
-    // ... entire block ...
-  });
+  // Consider removing the env "parsing" test; replace with an assertion
+  // that NODE_USE_SYSTEM_CA toggles trust behavior (covered by other tests).

105-169: Make platform test network‑gated and avoid noisy console output assertions

Gate with networkTest; avoid asserting on bespoke log strings; simply assert success.

-  test("should handle platform-specific behavior correctly", async () => {
+  networkTest("should handle platform-specific behavior correctly", async () => {
@@
-    console.log("Platform test output:", stdout);
-    console.log("Platform test errors:", stderr);
-
     expect(exitCode).toBe(0);
-    expect(stdout).toContain("SUCCESS: Platform-specific certificate loading working");
-
-    if (platform() === "darwin") {
-      expect(stdout).toContain("macOS Security framework integration should be active");
-    } else if (platform() === "linux") {
-      expect(stdout).toContain("Linux system certificate loading should be active");
-    }
+    expect(stdout).toContain("SUCCESS: Platform-specific certificate loading working");

171-237: Avoid hard timeouts in child script; gate TLS test behind network flag

Avoid explicit setTimeout in tests; rely on Bun’s built‑in timeouts and gating.

-  test("should work with TLS connections", async () => {
+  networkTest("should work with TLS connections", async () => {
@@
-    socket.setTimeout(10000, () => {
-      console.error('FAILED: Connection timeout');
-      socket.destroy();
-      reject(new Error('Timeout'));
-    });
+    // No explicit timeout; rely on test runner's timeout controls
test/js/node/tls/test-use-system-ca-flag.test.ts (3)

1-6: Import spawn from harness or Bun is fine; add network gating helper

Add gating for tests that hit external HTTPS endpoints; keep flag/env acceptance tests always-on.

 import { spawn } from "bun";
 import { expect, test } from "bun:test";
 import { bunEnv, bunExe } from "harness";
 
+const networkTest = process.env.BUN_TEST_ALLOW_NET === "1" ? test : test.skip;

36-59: Gate networked HTTPS test

-test("--use-system-ca with HTTPS request", async () => {
+networkTest("--use-system-ca with HTTPS request", async () => {

61-84: Gate networked HTTPS test

-test("NODE_USE_SYSTEM_CA=1 with HTTPS request", async () => {
+networkTest("NODE_USE_SYSTEM_CA=1 with HTTPS request", async () => {
test/js/node/tls/test-system-ca-https.test.ts (3)

12-35: Prefer deciding skip at definition time instead of inside the test body

Define a per-test function alias (skipped vs not) based on skipIfNoSystemCerts() before registering the test. Calling test.skip() inside a running test doesn’t mark it skipped.

-describe("system CA with HTTPS", () => {
-  // Skip test if no system certificates are available
-  const skipIfNoSystemCerts = () => {
+describe("system CA with HTTPS", () => {
+  // Skip test if no system certificates are available
+  const skipIfNoSystemCerts = () => {
@@
-  networkTest("HTTPS request with system CA", async () => {
-    const skipReason = skipIfNoSystemCerts();
-    if (skipReason) {
-      test.skip(skipReason);
-      return;
-    }
+  const skipReason = skipIfNoSystemCerts();
+  const maybeTest = skipReason ? test.skip : networkTest;
+
+  maybeTest("HTTPS request with system CA", async () => {

91-148: Same dynamic skip approach for second test

-  networkTest("HTTPS fails without system CA for custom root cert", async () => {
+  maybeTest("HTTPS fails without system CA for custom root cert", async () => {

94-114: Avoid assumptions about specific sites’ CAs

Using irs.gov as “often custom CA” is brittle. Prefer a deterministic local fixture in a follow‑up, or relax assertions (already partially done). Consider marking this as flaky-unsafe until hermetic fixtures exist.

src/bun.js/bindings/NodeTLS.cpp (1)

84-131: Harden BIO buffer checks and align error handling.

Guard against null bioData and treat PEM conversion/read failures consistently.

-        char* bioData;
-        long bioLen = BIO_get_mem_data(bio, &bioData);
-        if (bioLen <= 0) {
+        char* bioData = nullptr;
+        long bioLen = BIO_get_mem_data(bio, &bioData);
+        if (bioLen <= 0 || !bioData) {
             BIO_free(bio);
             continue;
         }

Also consider logging (throwError) on conversion failures (Lines 106-109) for parity with getExtraCACertificates(), or explicitly choose “best effort” semantics in a comment.

packages/bun-usockets/src/crypto/root_certs_linux.cpp (2)

4-12: Missing headers for error API and file type checks.

ERR_clear_error() and potential DER fallback need <openssl/err.h>; robust dir scanning benefits from <sys/stat.h>.

 #include <dirent.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <limits.h>
+#include <sys/stat.h>
+#include <openssl/err.h>
 #include <openssl/x509.h>
 #include <openssl/x509_vfy.h>
 #include <openssl/pem.h>

75-168: Align NODE_EXTRA_CA_CERTS path with helper and reduce duplication.

Reuse the bundle loader and propagate a warning if read fails; also consider filtering to CA certs with X509_check_ca to keep the stack tidy.

   // Also check NODE_EXTRA_CA_CERTS environment variable
   const char* extra_ca_certs = getenv("NODE_EXTRA_CA_CERTS");
   if (extra_ca_certs && strlen(extra_ca_certs) > 0) {
-    FILE* file = fopen(extra_ca_certs, "r");
-    if (file) {
-      X509* cert;
-      while ((cert = PEM_read_X509(file, NULL, NULL, NULL)) != NULL) {
-        sk_X509_push(*system_certs, cert);
-      }
-      fclose(file);
-    } else {
-      BUN__warn__extra_ca_load_failed(extra_ca_certs, "Failed to open file");
-    }
+    load_certs_from_bundle(extra_ca_certs, *system_certs);
+    // If the file couldn't be opened, surface a warning for parity with Node
+    FILE* probe = fopen(extra_ca_certs, "r");
+    if (!probe) {
+      BUN__warn__extra_ca_load_failed(extra_ca_certs, "Failed to open file");
+    } else {
+      fclose(probe);
+    }
   }

Please re-run the failing “test-openssl-ca-options.js” suites after this change; hashed-dir coverage is a common cause of cross‑platform failures reported by CI.

packages/bun-usockets/src/crypto/root_certs_darwin.cpp (3)

145-194: Dynamic constant loading is fine; tighten logging.

Current fprintf DEBUG lines are noisy in normal runs. Consider gating behind a verbose flag.


310-344: Trust evaluation fallback is acceptable; document simplification.

Given is_trust_settings_trusted_for_policy() returns UNSPECIFIED, a brief comment that we rely on SecTrustEvaluateWithError for final decision would prevent future regressions.


395-421: Check push result to avoid leaking X509 on allocation failure.

Handle sk_X509_push() failure.

-        if (X509_check_ca(x509_cert) == 1 &&
-            is_certificate_trusted_for_policy(security, x509_cert, cert_ref)) {
-            sk_X509_push(*system_certs, x509_cert);
-        } else {
-            X509_free(x509_cert);
-        }
+        if (X509_check_ca(x509_cert) == 1 &&
+            is_certificate_trusted_for_policy(security, x509_cert, cert_ref)) {
+            if (!sk_X509_push(*system_certs, x509_cert)) {
+                X509_free(x509_cert);
+            }
+        } else {
+            X509_free(x509_cert);
+        }
packages/bun-usockets/src/crypto/root_certs.cpp (5)

10-20: Trim unused POSIX includes on non-Windows builds.

These aren’t used here; prefer to include them only where needed to keep compile units lean.

-#else
-// Linux/Unix includes
-#include <dirent.h>
-#include <stdio.h>
-#include <limits.h>
-#endif
+#endif

31-42: Accept more truthy values for NODE_USE_SYSTEM_CA.

Match common env patterns (“1”, “true”, “yes”, “on”) to reduce friction.

-  const char *use_system_ca = getenv("NODE_USE_SYSTEM_CA");
-  return use_system_ca && strcmp(use_system_ca, "1") == 0;
+  const char *v = getenv("NODE_USE_SYSTEM_CA");
+  if (!v) return false;
+  return strcmp(v, "1") == 0 || strcasecmp(v, "true") == 0 ||
+         strcasecmp(v, "yes") == 0 || strcasecmp(v, "on") == 0;

250-256: Ignore duplicate-cert errors when adding to store.

X509_STORE_add_cert() raises X509_R_CERT_ALREADY_IN_HASH_TABLE on duplicates; clear benign errors to avoid polluting the error stack.

-    for (int i = 0; i < sk_X509_num(root_system_cert_instances); i++) {
-      X509 *cert = sk_X509_value(root_system_cert_instances, i);
-      X509_up_ref(cert);
-      X509_STORE_add_cert(store, cert);
-    }
+    for (int i = 0; i < sk_X509_num(root_system_cert_instances); i++) {
+      X509 *cert = sk_X509_value(root_system_cert_instances, i);
+      X509_up_ref(cert);
+      if (!X509_STORE_add_cert(store, cert)) {
+        unsigned long err = ERR_peek_last_error();
+        if (!(ERR_GET_LIB(err) == ERR_LIB_X509 &&
+              ERR_GET_REASON(err) == X509_R_CERT_ALREADY_IN_HASH_TABLE)) {
+          // keep other errors; consider logging if needed
+        } else {
+          ERR_clear_error();
+        }
+      }
+    }

Note: add <openssl/err.h> include at top of this file if not already present.


283-300: Check push result in Windows converter.

Prevent leaks on allocation failure.

-    X509* x509_cert = d2i_X509(NULL, &data, raw_cert.data.size());
-    if (x509_cert != NULL) {
-      sk_X509_push(*system_certs, x509_cert);
-    }
+    X509* x509_cert = d2i_X509(NULL, &data, raw_cert.data.size());
+    if (x509_cert != NULL) {
+      if (!sk_X509_push(*system_certs, x509_cert)) {
+        X509_free(x509_cert);
+      }
+    }

180-186: Add a compatibility wrapper for us_raw_root_certs.

If consumers still call us_raw_root_certs(), provide a thin wrapper to the new us_internal_raw_root_certs() to keep ABI stable.

Add this near us_internal_raw_root_certs():

extern "C" int us_raw_root_certs(struct us_cert_string_t **out) {
  return us_internal_raw_root_certs(out);
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0b54932 and 40d2637.

📒 Files selected for processing (24)
  • cmake/sources/CxxSources.txt (1 hunks)
  • cmake/targets/BuildBun.cmake (2 hunks)
  • packages/bun-usockets/src/crypto/root_certs.cpp (6 hunks)
  • packages/bun-usockets/src/crypto/root_certs_darwin.cpp (1 hunks)
  • packages/bun-usockets/src/crypto/root_certs_header.h (1 hunks)
  • packages/bun-usockets/src/crypto/root_certs_linux.cpp (1 hunks)
  • packages/bun-usockets/src/crypto/root_certs_platform.h (1 hunks)
  • packages/bun-usockets/src/crypto/root_certs_windows.cpp (1 hunks)
  • src/bun.js/bindings/NodeTLS.cpp (3 hunks)
  • src/bun.js/bindings/NodeTLS.h (1 hunks)
  • src/bun.zig (1 hunks)
  • src/cli/Arguments.zig (3 hunks)
  • src/js/node/tls.ts (4 hunks)
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts (1 hunks)
  • test/js/bun/fetch/node-use-system-ca.test.ts (1 hunks)
  • test/js/node/test/parallel/test-openssl-ca-options.js (1 hunks)
  • test/js/node/test/parallel/test-tls-get-ca-certificates-node-use-system-ca.js (1 hunks)
  • test/js/node/test/parallel/test-tls-get-ca-certificates-system.js (1 hunks)
  • test/js/node/test/system-ca/test-native-root-certs.test.mjs (1 hunks)
  • test/js/node/tls/test-node-extra-ca-certs.test.ts (1 hunks)
  • test/js/node/tls/test-system-ca-https.test.ts (1 hunks)
  • test/js/node/tls/test-use-system-ca-flag.test.ts (1 hunks)
  • test/js/node/tls/test-use-system-ca.test.ts (1 hunks)
  • test/no-validate-exceptions.txt (1 hunks)
🧰 Additional context used
📓 Path-based instructions (19)
**/*.{cpp,h}

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.{cpp,h}: When exposing a JS class with public Constructor and Prototype, define three C++ types: class Foo : public JSC::DestructibleObject (if it has C++ fields), class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
If the class has C++ data members, inherit from JSC::DestructibleObject and provide proper destruction; if it has no C++ fields (only JS properties), avoid a class and use JSC::constructEmptyObject(vm, structure) with putDirectOffset
Prefer placing the subspaceFor implementation in the .cpp file rather than the header when possible

Files:

  • src/bun.js/bindings/NodeTLS.h
  • packages/bun-usockets/src/crypto/root_certs_header.h
  • packages/bun-usockets/src/crypto/root_certs_platform.h
  • packages/bun-usockets/src/crypto/root_certs_darwin.cpp
  • packages/bun-usockets/src/crypto/root_certs_windows.cpp
  • packages/bun-usockets/src/crypto/root_certs_linux.cpp
  • src/bun.js/bindings/NodeTLS.cpp
  • packages/bun-usockets/src/crypto/root_certs.cpp
**/*.{cpp,cc,cxx,h,hpp,hxx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format C/C++ sources/headers with clang-format (bun run clang-format)

Files:

  • src/bun.js/bindings/NodeTLS.h
  • packages/bun-usockets/src/crypto/root_certs_header.h
  • packages/bun-usockets/src/crypto/root_certs_platform.h
  • packages/bun-usockets/src/crypto/root_certs_darwin.cpp
  • packages/bun-usockets/src/crypto/root_certs_windows.cpp
  • packages/bun-usockets/src/crypto/root_certs_linux.cpp
  • src/bun.js/bindings/NodeTLS.cpp
  • packages/bun-usockets/src/crypto/root_certs.cpp
test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place all tests under the test/ directory

Files:

  • test/js/node/test/parallel/test-openssl-ca-options.js
  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/test/parallel/test-tls-get-ca-certificates-node-use-system-ca.js
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/js/node/test/system-ca/test-native-root-certs.test.mjs
  • test/no-validate-exceptions.txt
  • test/js/node/test/parallel/test-tls-get-ca-certificates-system.js
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
  • test/js/node/tls/test-system-ca-https.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
test/js/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place JavaScript and TypeScript tests under test/js/

Files:

  • test/js/node/test/parallel/test-openssl-ca-options.js
  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/test/parallel/test-tls-get-ca-certificates-node-use-system-ca.js
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/js/node/test/parallel/test-tls-get-ca-certificates-system.js
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
  • test/js/node/tls/test-system-ca-https.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
test/js/node/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place Node.js module compatibility tests under test/js/node/, separated by module (e.g., test/js/node/assert/)

Files:

  • test/js/node/test/parallel/test-openssl-ca-options.js
  • test/js/node/test/parallel/test-tls-get-ca-certificates-node-use-system-ca.js
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/js/node/test/parallel/test-tls-get-ca-certificates-system.js
  • test/js/node/tls/test-system-ca-https.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
test/js/node/test/**

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Vendored Node.js tests under test/js/node/test/ are exceptions and do not follow Bun’s test style

Files:

  • test/js/node/test/parallel/test-openssl-ca-options.js
  • test/js/node/test/parallel/test-tls-get-ca-certificates-node-use-system-ca.js
  • test/js/node/test/system-ca/test-native-root-certs.test.mjs
  • test/js/node/test/parallel/test-tls-get-ca-certificates-system.js
test/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

test/**/*.{js,ts}: Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test
Prefer data-driven tests (e.g., test.each) to reduce boilerplate
Use shared utilities from test/harness.ts where applicable

Files:

  • test/js/node/test/parallel/test-openssl-ca-options.js
  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/test/parallel/test-tls-get-ca-certificates-node-use-system-ca.js
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/js/node/test/parallel/test-tls-get-ca-certificates-system.js
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
  • test/js/node/tls/test-system-ca-https.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
test/js/**

📄 CodeRabbit inference engine (test/CLAUDE.md)

Organize unit tests for specific features under test/js/ by module

Files:

  • test/js/node/test/parallel/test-openssl-ca-options.js
  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/test/parallel/test-tls-get-ca-certificates-node-use-system-ca.js
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/js/node/test/system-ca/test-native-root-certs.test.mjs
  • test/js/node/test/parallel/test-tls-get-ca-certificates-system.js
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
  • test/js/node/tls/test-system-ca-https.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format JavaScript/TypeScript files with Prettier (bun run prettier)

Files:

  • test/js/node/test/parallel/test-openssl-ca-options.js
  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/test/parallel/test-tls-get-ca-certificates-node-use-system-ca.js
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/js/node/test/parallel/test-tls-get-ca-certificates-system.js
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
  • test/js/node/tls/test-system-ca-https.test.ts
  • src/js/node/tls.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
**/*.cpp

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.cpp: Include "root.h" at the top of C++ binding files to satisfy lints
Define prototype properties using a const HashTableValue array and declare accessors/functions with JSC_DECLARE_* macros
Prototype classes should subclass JSC::JSNonFinalObject, provide create/createStructure, DECLARE_INFO, finishCreation that reifies static properties, and set mayBePrototype on the Structure
Custom getters should use JSC_DEFINE_CUSTOM_GETTER, jsDynamicCast to validate this, and throwThisTypeError on mismatch
Custom setters should use JSC_DEFINE_CUSTOM_SETTER, validate this via jsDynamicCast, and store via WriteBarrier/set semantics
Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue
Constructors should subclass JSC::InternalFunction, return internalFunctionSpace in subspaceFor, set the prototype property as non-configurable/non-writable, and provide create/createStructure
Provide a setup function that builds the Prototype, Constructor, and Structure, and assigns them to the LazyClassStructure initializer
Use the cached Structure via globalObject->m_.get(globalObject) when constructing instances
Expose constructors to Zig via an extern "C" function that returns the constructor from the LazyClassStructure
Provide an extern "C" Bun____toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Files:

  • packages/bun-usockets/src/crypto/root_certs_darwin.cpp
  • packages/bun-usockets/src/crypto/root_certs_windows.cpp
  • packages/bun-usockets/src/crypto/root_certs_linux.cpp
  • src/bun.js/bindings/NodeTLS.cpp
  • packages/bun-usockets/src/crypto/root_certs.cpp
test/js/bun/**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/writing-tests.mdc)

Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Files:

  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
test/**/*.test.ts

📄 CodeRabbit inference engine (test/CLAUDE.md)

test/**/*.test.ts: Name test files *.test.ts and use bun:test
Do not write flaky tests: never wait for arbitrary time; wait for conditions instead
Never hardcode port numbers in tests; use port: 0 to get a random port
When spawning Bun in tests, use bunExe() and bunEnv from harness
Prefer async/await in tests; for a single callback, use Promise.withResolvers()
Do not set explicit test timeouts; rely on Bun’s built-in timeouts
Use tempDir/tempDirWithFiles from harness for temporary files and directories in tests
For large/repetitive strings in tests, prefer Buffer.alloc(count, fill).toString() over "A".repeat(count)
Import common test utilities from harness (e.g., bunExe, bunEnv, tempDirWithFiles, tmpdirSync, platform checks, GC helpers)
In error tests, assert non-zero exit codes for failing processes and use toThrow for synchronous errors
Use describe blocks for grouping, describe.each for parameterized tests, snapshots with toMatchSnapshot, and lifecycle hooks (beforeAll, beforeEach, afterEach); track resources for cleanup in afterEach
Use using/await using with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Files:

  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
  • test/js/node/tls/test-system-ca-https.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
test/**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

test/**/*.test.{ts,tsx}: Test files must be placed under test/ and end with .test.ts or .test.tsx
In tests, always use port: 0; do not hardcode ports or use custom random port functions
In tests, use normalizeBunSnapshot when asserting snapshots
Never write tests that merely assert absence of "panic" or "uncaught exception" in output
Avoid shell commands (e.g., find, grep) in tests; use Bun.Glob and built-ins instead
Prefer snapshot tests over exact stdout equality assertions

Files:

  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
  • test/js/node/tls/test-system-ca-https.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/bun.zig
  • src/cli/Arguments.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

**/*.zig: Format Zig files with zig-format (bun run zig-format)
In Zig, manage memory carefully with allocators and use defer for cleanup

Files:

  • src/bun.zig
  • src/cli/Arguments.zig
src/bun.js/bindings/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.cpp: For JS classes with a public constructor, implement Foo, FooPrototype, and FooConstructor (JSC patterns)
Define class properties using HashTableValue arrays in JSC bindings
Add iso subspaces for classes with C++ fields in JSC bindings
Cache structures in ZigGlobalObject for JSC-bound classes

Files:

  • src/bun.js/bindings/NodeTLS.cpp
src/js/node/**/*.{js,ts}

📄 CodeRabbit inference engine (src/js/CLAUDE.md)

Place Node.js compatibility modules (e.g., node:fs, node:path) under node/

Files:

  • src/js/node/tls.ts
src/js/{builtins,node,bun,thirdparty,internal}/**/*.{js,ts}

📄 CodeRabbit inference engine (src/js/CLAUDE.md)

src/js/{builtins,node,bun,thirdparty,internal}/**/*.{js,ts}: Use .$call and .$apply instead of .call or .apply to avoid user tampering
Use string-literal require("...") only (no dynamic or non-literal specifiers)
Author modules as CommonJS-style with require(...) and export via export default {} (no ESM import/named exports)
Prefer JSC intrinsics and $-prefixed private APIs for performance and safety (e.g., $Array, $newArrayWithSize, $putByIdDirectPrivate, $assert, $debug)
Validate function arguments with $isCallable and throw $ERR_INVALID_ARG_TYPE for invalid callbacks
Use $isObject and throw appropriate TypeErrors for constructor/initializer inputs that must be objects

Files:

  • src/js/node/tls.ts
src/js/**/*.{js,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

src/js/**/*.{js,ts}: In built-in modules (src/js), require() must use string literals resolved at compile time
In built-in modules (src/js), use export default (converted to a return statement by the preprocessor)

Files:

  • src/js/node/tls.ts
🧠 Learnings (39)
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.txt : Add new V8 API mangled symbols (without leading underscore) to src/symbols.txt

Applied to files:

  • src/bun.js/bindings/NodeTLS.h
  • cmake/sources/CxxSources.txt
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/bun/**/*.{js,ts} : Place Bun API tests under test/js/bun/, separated by category (e.g., test/js/bun/glob/)

Applied to files:

  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : When spawning Bun in tests, use `bunExe()` and `bunEnv` from `harness`

Applied to files:

  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
  • test/js/node/tls/test-system-ca-https.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/**/*.{js,ts} : Write tests in JavaScript or TypeScript using Bun’s Jest-style APIs (test, describe, expect) and run with bun test

Applied to files:

  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/node/test/** : Vendored Node.js tests under test/js/node/test/ are exceptions and do not follow Bun’s test style

Applied to files:

  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/no-validate-exceptions.txt
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/cli/**/*.{js,ts} : When testing Bun as a CLI, use spawn with bunExe() and bunEnv from harness, and capture stdout/stderr via pipes

Applied to files:

  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
  • test/js/node/tls/test-system-ca-https.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/bundle.test.ts : bundle.test.ts should contain DevServer-specific bundling tests

Applied to files:

  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/{dev/*.test.ts,dev-and-prod.ts} : Do not use node:fs APIs in tests; mutate files via dev.write, dev.patch, and dev.delete

Applied to files:

  • test/js/bun/fetch/node-use-system-ca.test.ts
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Name test files `*.test.ts` and use `bun:test`

Applied to files:

  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Import common test utilities from `harness` (e.g., `bunExe`, `bunEnv`, `tempDirWithFiles`, `tmpdirSync`, platform checks, GC helpers)

Applied to files:

  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/test/v8/v8.test.ts : Add a corresponding test case in test/v8/v8.test.ts that invokes checkSameOutput with the new function

Applied to files:

  • test/js/bun/fetch/node-use-system-ca.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: For Node.js compatibility bugs, validate expected behavior in Node.js first, then confirm failure on Bun canary

Applied to files:

  • test/js/bun/fetch/node-use-system-ca.test.ts
  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/{**/js_*.zig,bun.js/api/**/*.zig} : Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Applied to files:

  • src/bun.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/bun.zig
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Applied to files:

  • src/bun.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)

Applied to files:

  • src/bun.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/bun.zig
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : Add new V8 API method mangled symbols to the V8API struct in src/napi/napi.zig for both GCC/Clang and MSVC

Applied to files:

  • src/bun.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions

Applied to files:

  • src/bun.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/bun.js/api/BunObject.zig : Create a getter function in BunObject.zig (define const Getter with toJSGetter and export it in exportAll)

Applied to files:

  • src/bun.zig
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/node/**/*.{js,ts} : Place Node.js module compatibility tests under test/js/node/, separated by module (e.g., test/js/node/assert/)

Applied to files:

  • test/js/node/test/parallel/test-tls-get-ca-certificates-node-use-system-ca.js
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
  • test/no-validate-exceptions.txt
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Add iso subspaces for classes with C++ fields in JSC bindings

Applied to files:

  • cmake/sources/CxxSources.txt
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Define class properties using HashTableValue arrays in JSC bindings

Applied to files:

  • cmake/sources/CxxSources.txt
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : For JS classes with a public constructor, implement Foo, FooPrototype, and FooConstructor (JSC patterns)

Applied to files:

  • cmake/sources/CxxSources.txt
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/V8*.h : Ensure all public V8 API functions are marked with BUN_EXPORT for symbol visibility

Applied to files:

  • cmake/sources/CxxSources.txt
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/V8*.cpp : Implement new V8 API classes in src/bun.js/bindings/v8/ as V8<Class>.cpp and include v8_compatibility_assertions.h with ASSERT_V8_TYPE_LAYOUT_MATCHES for the class

Applied to files:

  • cmake/sources/CxxSources.txt
📚 Learning: 2025-09-07T05:41:52.563Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-09-07T05:41:52.563Z
Learning: Applies to src/js/bun/**/*.{js,ts} : Place Bun-specific modules (e.g., `bun:ffi`, `bun:sqlite`) under `bun/`

Applied to files:

  • cmake/sources/CxxSources.txt
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.dyn : Add new V8 API mangled symbols (with leading underscore and semicolons) to src/symbols.dyn

Applied to files:

  • cmake/sources/CxxSources.txt
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Cache structures in ZigGlobalObject for JSC-bound classes

Applied to files:

  • cmake/sources/CxxSources.txt
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/**/*.{h,cpp} : Use V8_UNIMPLEMENTED() for functions that are not yet implemented

Applied to files:

  • cmake/sources/CxxSources.txt
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Update src/bun.js/bindings/generated_classes_list.zig to include new classes

Applied to files:

  • cmake/sources/CxxSources.txt
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/dev/esm.test.ts : esm.test.ts should cover ESM feature behavior in development mode

Applied to files:

  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to test/**/*.test.{ts,tsx} : In tests, use normalizeBunSnapshot when asserting snapshots

Applied to files:

  • test/js/node/tls/test-use-system-ca.test.ts
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Do not set explicit test timeouts; rely on Bun’s built-in timeouts

Applied to files:

  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-node-extra-ca-certs.test.ts
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/**/*.test.ts : Use `using`/`await using` with Bun resources (e.g., Bun.listen/connect/spawn/serve) to ensure cleanup in tests

Applied to files:

  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/node/tls/test-use-system-ca-flag.test.ts
📚 Learning: 2025-08-30T00:09:39.100Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/dev-server-tests.mdc:0-0
Timestamp: 2025-08-30T00:09:39.100Z
Learning: Applies to test/bake/bake-harness.ts : Provide and maintain shared test utilities: devTest, prodTest, devAndProductionTest, Dev, Client, and helpers in the harness

Applied to files:

  • test/js/node/tls/test-use-system-ca.test.ts
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
📚 Learning: 2025-08-30T00:12:56.803Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/writing-tests.mdc:0-0
Timestamp: 2025-08-30T00:12:56.803Z
Learning: Applies to test/js/third_party/**/*.{js,ts} : Place third-party npm package tests under test/js/third_party/

Applied to files:

  • test/no-validate-exceptions.txt
📚 Learning: 2025-09-03T17:10:13.486Z
Learnt from: CR
PR: oven-sh/bun#0
File: test/CLAUDE.md:0-0
Timestamp: 2025-09-03T17:10:13.486Z
Learning: Applies to test/js/** : Organize unit tests for specific features under `test/js/` by module

Applied to files:

  • test/no-validate-exceptions.txt
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Follow existing patterns in similar V8 classes, add comprehensive Node.js parity tests, update all symbol files, and document any special behavior

Applied to files:

  • test/no-validate-exceptions.txt
  • test/js/bun/fetch/node-use-system-ca-complete.test.ts
🧬 Code graph analysis (9)
packages/bun-usockets/src/crypto/root_certs_platform.h (1)
packages/bun-usockets/src/crypto/root_certs_darwin.cpp (2)
  • us_cleanup_security_framework (424-429)
  • us_cleanup_security_framework (424-424)
packages/bun-usockets/src/crypto/root_certs_darwin.cpp (1)
src/bun.js/bindings/SecretsDarwin.cpp (1)
  • CFRelease (136-158)
test/js/bun/fetch/node-use-system-ca.test.ts (1)
test/harness.ts (1)
  • tempDirWithFiles (259-266)
test/js/node/test/parallel/test-tls-get-ca-certificates-node-use-system-ca.js (2)
test/js/node/test/parallel/test-openssl-ca-options.js (1)
  • common (4-4)
test/js/node/test/parallel/test-tls-get-ca-certificates-system.js (4)
  • common (6-6)
  • require (11-11)
  • tls (10-10)
  • systemCerts (13-13)
test/js/node/tls/test-node-extra-ca-certs.test.ts (1)
test/harness.ts (1)
  • tempDirWithFiles (259-266)
test/js/bun/fetch/node-use-system-ca-complete.test.ts (1)
test/harness.ts (1)
  • tempDirWithFiles (259-266)
src/bun.js/bindings/NodeTLS.cpp (1)
src/js/node/tls.ts (1)
  • rootCertificates (1032-1034)
test/js/node/tls/test-system-ca-https.test.ts (1)
test/harness.ts (1)
  • tempDirWithFiles (259-266)
src/js/node/tls.ts (1)
src/js/internal/util/inspect.js (1)
  • ArrayPrototypePush (56-56)
🔇 Additional comments (18)
test/no-validate-exceptions.txt (1)

101-125: Confirm correct bucket for this test

Please verify this test truly belongs under “potentially throw inside of reifyStaticProperties” (vs. the ASAN validateExceptionChecks list above). Misclassification can mask useful signal in CI.

cmake/targets/BuildBun.cmake (2)

972-973: Good: delayload CRYPT32 on Windows release

This pairs well with delayimp and reduces startup cost. Ensure the code handles late-binding failures gracefully.


1185-1195: Linking crypt32 on Windows

Correct dependency for system CA access. Please confirm this also works on the baseline runner variants (x64-baseline) and no extra stubs are required.

test/js/bun/fetch/node-use-system-ca.test.ts (1)

6-9: Net-gated tests: OK

Gating external network hits behind BUN_TEST_ALLOW_NET is aligned with our flake policy.

src/bun.js/bindings/NodeTLS.h (1)

9-9: Host function declaration looks good; check symbol exports

Add/verify any required symbol entries (symbols.txt/.dyn/.def) if this function must be exported for embedding scenarios.

test/js/node/tls/test-use-system-ca.test.ts (1)

54-68: Precedence case: OK

Flag takes precedence over env; assertions and plumbing look fine.

cmake/sources/CxxSources.txt (1)

113-115: Likely typo: ncrpyto_engine.cpp won’t compile

File name appears misspelled; build will fail if the path doesn’t exist.

-src/bun.js/bindings/ncrpyto_engine.cpp
+src/bun.js/bindings/ncrypto_engine.cpp

Run to confirm the real filename:

test/js/node/test/parallel/test-openssl-ca-options.js (1)

11-19: Node parity: CLI should exit with code 9 and exact error text

Test looks correct for Node’s behavior. CI shows widespread exit code 1 for this case; that points to Bun’s CLI not matching Node (status 9 + exact stderr with OS EOL). Please fix the flag parsing/exit path instead of relaxing the test.

src/cli/Arguments.zig (1)

107-116: LGTM: New CA selection flags and mutual exclusion check

Flags are documented and parsed under runtime params; the XOR check prevents invalid combinations.

src/js/node/tls.ts (1)

934-935: LGTM: Zig binding present and signature matches

Found pub fn getUseSystemCA(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) error{ JSError, OutOfMemory }!jsc.JSValue at src/bun.zig:3786 — the $newZigFunction("bun.zig", "getUseSystemCA", 0) call in src/js/node/tls.ts is correct.

packages/bun-usockets/src/crypto/root_certs_windows.cpp (1)

33-39: No explicit CertFreeCertificateContext needed after enumeration

Microsoft Docs state CertEnumCertificatesInStore frees the previous non-NULL pPrevCertContext even when it returns NULL, so the loop shown is correct and does not require an extra CertFreeCertificateContext call.

src/bun.js/bindings/NodeTLS.cpp (2)

48-82: Good: switch to MarkedArgumentBuffer + overflow handling.

Buffered construction + freeze reduces churn and adds OOM checks. Looks solid.


19-36: Incorrect — wrapper exists; no action required.
packages/bun-usockets/src/context.c defines us_raw_root_certs(...) that forwards to us_internal_raw_root_certs; libusockets.h/internal/internal.h declare the symbols and packages/bun-usockets/src/crypto/root_certs.cpp defines us_internal_raw_root_certs.

Likely an incorrect or invalid review comment.

packages/bun-usockets/src/crypto/root_certs_linux.cpp (1)

56-74: OK, with small push-failure handling.

Loop and ERR_clear_error() are fine. You already free on push failure; good.

packages/bun-usockets/src/crypto/root_certs.cpp (4)

164-173: OK: platform-gated system CA loading.

Single-path init is fine; macOS/Windows/Linux split looks sane.


191-197: Good: extend default bundle with system certs field.

Initialization order is correct.


205-211: Accessor guards are good; minor nit: ensure init happened.

Calling us_get_default_ca_certificates() ensures init; returning NULL when disabled is correct.


219-226: Avoid double-loading system root certificates

packages/bun-usockets/src/crypto/root_certs.cpp (lines 219–226) calls X509_STORE_set_default_paths(store) when us_should_use_system_ca() is true — if the codebase also injects system certs (root_system_cert_instances) you are both setting OpenSSL default paths and adding the same certs manually, which can diverge from Node's --use-openssl-ca / --use-bundled-ca semantics (see test/js/node/test/parallel/test-openssl-ca-options.js and the NODE_USE_SYSTEM_CA tests).

  • Action: choose one behavior — either rely on X509_STORE_set_default_paths() for OpenSSL/system CA mode, or manually push system cert instances and skip X509_STORE_set_default_paths(). Re-run the TLS/system-CA tests (test-openssl-ca-options.js, test-use-system-ca.* tests, and the NODE_USE_SYSTEM_CA test suite) to verify expected semantics.

Comment on lines +15 to +54
// Helper function to load certificates from a directory
static void load_certs_from_directory(const char* dir_path, STACK_OF(X509)* cert_stack) {
DIR* dir = opendir(dir_path);
if (!dir) {
return;
}

struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
// Skip . and ..
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}

// Check if file has .crt, .pem, or .cer extension
const char* ext = strrchr(entry->d_name, '.');
if (!ext || (strcmp(ext, ".crt") != 0 && strcmp(ext, ".pem") != 0 && strcmp(ext, ".cer") != 0)) {
continue;
}

// Build full path
char filepath[PATH_MAX];
snprintf(filepath, sizeof(filepath), "%s/%s", dir_path, entry->d_name);

// Try to load certificate
FILE* file = fopen(filepath, "r");
if (file) {
X509* cert = PEM_read_X509(file, NULL, NULL, NULL);
fclose(file);

if (cert) {
if (!sk_X509_push(cert_stack, cert)) {
X509_free(cert);
}
}
}
}

closedir(dir);
}

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.

⚠️ Potential issue

Directory scan skips hashed OpenSSL entries; many distros will be missed.

Filtering by extensions ignores hashed symlinks (e.g., abcd1234.0) under /etc/ssl/certs, leading to large trust gaps and test failures.

 static void load_certs_from_directory(const char* dir_path, STACK_OF(X509)* cert_stack) {
   DIR* dir = opendir(dir_path);
   if (!dir) {
     return;
   }
   
   struct dirent* entry;
   while ((entry = readdir(dir)) != NULL) {
-    // Skip . and ..
+    // Skip . and ..
     if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
       continue;
     }
-    
-    // Check if file has .crt, .pem, or .cer extension
-    const char* ext = strrchr(entry->d_name, '.');
-    if (!ext || (strcmp(ext, ".crt") != 0 && strcmp(ext, ".pem") != 0 && strcmp(ext, ".cer") != 0)) {
-      continue;
-    }
-    
-    // Build full path
+
+    // Build full path
     char filepath[PATH_MAX];
     snprintf(filepath, sizeof(filepath), "%s/%s", dir_path, entry->d_name);
-    
-    // Try to load certificate
-    FILE* file = fopen(filepath, "r");
-    if (file) {
-      X509* cert = PEM_read_X509(file, NULL, NULL, NULL);
-      fclose(file);
-      
-      if (cert) {
-        if (!sk_X509_push(cert_stack, cert)) {
-          X509_free(cert);
-        }
-      }
-    }
+
+    struct stat st;
+    if (stat(filepath, &st) != 0 || !(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
+      continue;
+    }
+
+    // Try PEM first, then DER
+    FILE* file = fopen(filepath, "rb");
+    if (!file) continue;
+    X509* cert = PEM_read_X509(file, NULL, NULL, NULL);
+    if (!cert) {
+      rewind(file);
+      cert = d2i_X509_fp(file, NULL); // DER fallback
+      ERR_clear_error();
+    }
+    fclose(file);
+    if (cert) {
+      if (!sk_X509_push(cert_stack, cert)) {
+        X509_free(cert);
+      }
+    }
   }
   
   closedir(dir);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Helper function to load certificates from a directory
static void load_certs_from_directory(const char* dir_path, STACK_OF(X509)* cert_stack) {
DIR* dir = opendir(dir_path);
if (!dir) {
return;
}
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
// Skip . and ..
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
// Check if file has .crt, .pem, or .cer extension
const char* ext = strrchr(entry->d_name, '.');
if (!ext || (strcmp(ext, ".crt") != 0 && strcmp(ext, ".pem") != 0 && strcmp(ext, ".cer") != 0)) {
continue;
}
// Build full path
char filepath[PATH_MAX];
snprintf(filepath, sizeof(filepath), "%s/%s", dir_path, entry->d_name);
// Try to load certificate
FILE* file = fopen(filepath, "r");
if (file) {
X509* cert = PEM_read_X509(file, NULL, NULL, NULL);
fclose(file);
if (cert) {
if (!sk_X509_push(cert_stack, cert)) {
X509_free(cert);
}
}
}
}
closedir(dir);
}
// Helper function to load certificates from a directory
static void load_certs_from_directory(const char* dir_path, STACK_OF(X509)* cert_stack) {
DIR* dir = opendir(dir_path);
if (!dir) {
return;
}
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
// Skip . and ..
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
continue;
}
// Build full path
char filepath[PATH_MAX];
snprintf(filepath, sizeof(filepath), "%s/%s", dir_path, entry->d_name);
struct stat st;
if (stat(filepath, &st) != 0 || !(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode))) {
continue;
}
// Try PEM first, then DER
FILE* file = fopen(filepath, "rb");
if (!file) continue;
X509* cert = PEM_read_X509(file, NULL, NULL, NULL);
if (!cert) {
rewind(file);
cert = d2i_X509_fp(file, NULL); // DER fallback
ERR_clear_error();
}
fclose(file);
if (cert) {
if (!sk_X509_push(cert_stack, cert)) {
X509_free(cert);
}
}
}
closedir(dir);
}

Comment on lines +8 to +16
// Load system certificates for the current platform
void us_load_system_certificates_linux(STACK_OF(X509) **system_certs);
void us_load_system_certificates_macos(STACK_OF(X509) **system_certs);
void us_load_system_certificates_windows(STACK_OF(X509) **system_certs);

// Platform-specific cleanup functions
#ifdef __APPLE__
void us_cleanup_security_framework();
#endif

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.

⚠️ Potential issue

🧩 Analysis chain

Windows entrypoint declared; ensure implementation (and linkage) exists

The header correctly declares us_load_system_certificates_windows(STACK_OF(X509) **). The Windows .cpp currently only exports a RAW variant. Implement the declared symbol (see suggested patch in root_certs_windows.cpp) to resolve linker/behavior gaps.

Run to confirm symbols:


🏁 Script executed:

#!/bin/bash
rg -nP 'us_load_system_certificates_windows\(' -C2
rg -nP 'us_load_system_certificates_windows_raw\(' -C2 || true

Length of output: 276844


**Implement the missing us_load_system_certificates_windows(STACK_OF(X509) ) in the Windows TU

root_certs_platform.h declares us_load_system_certificates_windows(STACK_OF(X509) **), but packages/bun-usockets/src/crypto/root_certs_windows.cpp only defines us_load_system_certificates_windows_raw(...), while packages/bun-usockets/src/crypto/root_certs.cpp calls the non‑raw symbol — this leaves an unresolved symbol/linker/runtime gap.

Action: add an extern "C" implementation named us_load_system_certificates_windows(STACK_OF(X509) **system_certs) in packages/bun-usockets/src/crypto/root_certs_windows.cpp that:

  • Calls the existing us_load_system_certificates_windows_raw helper to obtain raw DER blobs.
  • Converts each blob to X509 via d2i_X509, pushes into a new STACK_OF(X509) (check sk_X509_push return and X509_free on failure).
  • Sets *system_certs (or NULL on error) and preserves proper refcounting/cleanup.
  • Include the shared header (root_certs_platform.h) and ensure extern "C" linkage matches the declaration.

Files to edit:

  • packages/bun-usockets/src/crypto/root_certs_platform.h (declaration — confirm exact signature)
  • packages/bun-usockets/src/crypto/root_certs_windows.cpp (implement wrapper + include header)
  • packages/bun-usockets/src/crypto/root_certs.cpp (verify call site remains correct after change)
🤖 Prompt for AI Agents
In packages/bun-usockets/src/crypto/root_certs_platform.h around lines 8-16,
confirm the exact declaration void
us_load_system_certificates_windows(STACK_OF(X509) **system_certs); then in
packages/bun-usockets/src/crypto/root_certs_windows.cpp implement an extern "C"
wrapper named us_load_system_certificates_windows(STACK_OF(X509) **system_certs)
that includes root_certs_platform.h, calls the existing
us_load_system_certificates_windows_raw helper to obtain an array/vector of raw
DER blobs, for each blob converts it to an X509* via d2i_X509, pushes it onto a
newly created STACK_OF(X509) using sk_X509_push (check the return and call
X509_free on failure to avoid leaks), on any error free created X509s and the
stack and set *system_certs = NULL, otherwise set *system_certs to the populated
stack and preserve correct refcounts; ensure extern "C" linkage matches the
header and do not change the call site in
packages/bun-usockets/src/crypto/root_certs.cpp.

Comment on lines +21 to +27
HCERTSTORE cert_store = CertOpenStore(
CERT_STORE_PROV_SYSTEM_W,
0,
0,
store_flags | CERT_STORE_READONLY_FLAG,
store_name
);

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.

⚠️ Potential issue

Open store with OPEN_EXISTING to avoid creating/altering system stores

CertOpenStore should use CERT_STORE_OPEN_EXISTING_FLAG with CERT_STORE_READONLY_FLAG. Without it, behavior can vary and may attempt to create a store under some contexts.

-  HCERTSTORE cert_store = CertOpenStore(
+  HCERTSTORE cert_store = CertOpenStore(
     CERT_STORE_PROV_SYSTEM_W,
     0,
     0,
-    store_flags | CERT_STORE_READONLY_FLAG,
+    store_flags | CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG,
     store_name
   );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
HCERTSTORE cert_store = CertOpenStore(
CERT_STORE_PROV_SYSTEM_W,
0,
0,
store_flags | CERT_STORE_READONLY_FLAG,
store_name
);
HCERTSTORE cert_store = CertOpenStore(
CERT_STORE_PROV_SYSTEM_W,
0,
0,
store_flags | CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG,
store_name
);
🤖 Prompt for AI Agents
In packages/bun-usockets/src/crypto/root_certs_windows.cpp around lines 21 to
27, the CertOpenStore call currently omits CERT_STORE_OPEN_EXISTING_FLAG which
can cause the system store to be created or altered in some contexts; update the
flags passed to CertOpenStore to include CERT_STORE_OPEN_EXISTING_FLAG in
addition to CERT_STORE_READONLY_FLAG (i.e., combine store_flags |
CERT_STORE_READONLY_FLAG | CERT_STORE_OPEN_EXISTING_FLAG) so the store is opened
read-only and will not be created or modified.

Comment on lines +46 to +51
extern void us_load_system_certificates_windows_raw(
std::vector<RawCertificate>& raw_certs) {
// Load only from ROOT by default
LoadRawCertsFromStore(raw_certs, CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT");
LoadRawCertsFromStore(raw_certs, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"ROOT");
}

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.

⚠️ Potential issue

Mismatch with declared platform API: implement us_load_system_certificates_windows() and keep RAW helper internal

Header root_certs_platform.h declares extern "C" void us_load_system_certificates_windows(STACK_OF(X509) **), but this file exports a C++-mangled us_load_system_certificates_windows_raw(std::vector&). Callers won’t be able to link/use Windows system CAs, which matches the widespread TLS test failures.

Implement the declared symbol and convert DER→X509; keep the raw loader as a private helper. Also use extern "C" for the exported function.

Apply this additive patch (keeps your RAW path; wires the official entrypoint):

@@
-// Main function to load raw system certificates on Windows
-// Returns certificates as raw DER data to avoid OpenSSL header conflicts
-extern void us_load_system_certificates_windows_raw(
-    std::vector<RawCertificate>& raw_certs) {
+// Main function to load raw system certificates on Windows (RAW, internal)
+static void us_load_system_certificates_windows_raw(
+    std::vector<RawCertificate>& raw_certs) {
   // Load only from ROOT by default
   LoadRawCertsFromStore(raw_certs, CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT");
   LoadRawCertsFromStore(raw_certs, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"ROOT");
 }
 
+#include <openssl/x509.h>
+#include <openssl/err.h>
+#undef X509_NAME // avoid WinCrypt macro collision if present
+
+extern "C" void us_load_system_certificates_windows(STACK_OF(X509) **system_certs) {
+  if (!system_certs)
+    return;
+  STACK_OF(X509) *out = sk_X509_new_null();
+  if (!out)
+    return;
+
+  std::vector<RawCertificate> raw;
+  us_load_system_certificates_windows_raw(raw);
+
+  for (const auto& rc : raw) {
+    const unsigned char* p = rc.data.data();
+    X509* x = d2i_X509(nullptr, &p, static_cast<long>(rc.data.size()));
+    if (x) {
+      sk_X509_push(out, x); // ownership transfers to the stack
+    } else {
+      ERR_clear_error(); // ignore malformed entries
+    }
+  }
+  *system_certs = out;
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
extern void us_load_system_certificates_windows_raw(
std::vector<RawCertificate>& raw_certs) {
// Load only from ROOT by default
LoadRawCertsFromStore(raw_certs, CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT");
LoadRawCertsFromStore(raw_certs, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"ROOT");
}
static void us_load_system_certificates_windows_raw(
std::vector<RawCertificate>& raw_certs) {
// Load only from ROOT by default
LoadRawCertsFromStore(raw_certs, CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT");
LoadRawCertsFromStore(raw_certs, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"ROOT");
}
#include <openssl/x509.h>
#include <openssl/err.h>
#undef X509_NAME // avoid WinCrypt macro collision if present
extern "C" void us_load_system_certificates_windows(STACK_OF(X509) **system_certs) {
if (!system_certs)
return;
STACK_OF(X509) *out = sk_X509_new_null();
if (!out)
return;
std::vector<RawCertificate> raw;
us_load_system_certificates_windows_raw(raw);
for (const auto& rc : raw) {
const unsigned char* p = rc.data.data();
X509* x = d2i_X509(nullptr, &p, static_cast<long>(rc.data.size()));
if (x) {
sk_X509_push(out, x); // ownership transfers to the stack
} else {
ERR_clear_error(); // ignore malformed entries
}
}
*system_certs = out;
}
🤖 Prompt for AI Agents
In packages/bun-usockets/src/crypto/root_certs_windows.cpp around lines 46 to
51, the file currently defines a C++-mangled helper
us_load_system_certificates_windows_raw(std::vector<RawCertificate>&) but the
public platform API declares extern "C" void
us_load_system_certificates_windows(STACK_OF(X509)**); implement that exact
extern "C" function here, keep the existing RAW helper private, call it to
populate a std::vector<RawCertificate>, convert each RawCertificate's DER blob
into an OpenSSL X509* (using d2i_X509 or equivalent), push each X509* onto the
provided STACK_OF(X509)* (initializing it if null), and ensure proper error
handling and memory ownership (free RawCertificate buffers and X509* on
failure); mark only the exported wrapper with extern "C" so callers link
correctly while retaining the raw loader as an internal helper.

Comment on lines +7 to +58
describe("NODE_USE_SYSTEM_CA Complete Implementation", () => {
test("should work with standard HTTPS sites", async () => {
const testDir = tempDirWithFiles("node-use-system-ca-basic", {});

const testScript = `
async function testHttpsRequest() {
try {
const response = await fetch('https://httpbin.org/user-agent');
console.log('SUCCESS: GitHub request completed with status', response.status);
process.exit(0);
} catch (error) {
console.log('ERROR: HTTPS request failed:', error.message);
process.exit(1);
}
}

testHttpsRequest();
`;

await fs.writeFile(join(testDir, "test.js"), testScript);

// Test with NODE_USE_SYSTEM_CA=1
const proc1 = Bun.spawn({
cmd: [bunExe(), "test.js"],
env: {
...bunEnv,
NODE_USE_SYSTEM_CA: "1",
},
cwd: testDir,
stdout: "pipe",
stderr: "pipe",
});

const [stdout1, stderr1, exitCode1] = await Promise.all([proc1.stdout.text(), proc1.stderr.text(), proc1.exited]);

expect(exitCode1).toBe(0);
expect(stdout1).toContain("SUCCESS");

// Test without NODE_USE_SYSTEM_CA
const proc2 = Bun.spawn({
cmd: [bunExe(), "test.js"],
env: bunEnv,
cwd: testDir,
stdout: "pipe",
stderr: "pipe",
});

const [stdout2, stderr2, exitCode2] = await Promise.all([proc2.stdout.text(), proc2.stderr.text(), proc2.exited]);

expect(exitCode2).toBe(0);
expect(stdout2).toContain("SUCCESS");
});

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.

⚠️ Potential issue

Gate networked tests to reduce CI flakes; fix misleading log string

These tests hit external endpoints and should be gated behind BUN_TEST_ALLOW_NET per our rules. Also, message says “GitHub” while calling httpbin.org.

-import { describe, expect, test } from "bun:test";
+import { describe, expect, test } from "bun:test";
@@
-describe("NODE_USE_SYSTEM_CA Complete Implementation", () => {
-  test("should work with standard HTTPS sites", async () => {
+const networkTest = process.env.BUN_TEST_ALLOW_NET === "1" ? test : test.skip;
+
+describe("NODE_USE_SYSTEM_CA Complete Implementation", () => {
+  networkTest("should work with standard HTTPS sites", async () => {
@@
-    const response = await fetch('https://httpbin.org/user-agent');
-    console.log('SUCCESS: GitHub request completed with status', response.status);
+    const response = await fetch('https://httpbin.org/user-agent');
+    console.log('SUCCESS: HTTPS request completed with status', response.status);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
describe("NODE_USE_SYSTEM_CA Complete Implementation", () => {
test("should work with standard HTTPS sites", async () => {
const testDir = tempDirWithFiles("node-use-system-ca-basic", {});
const testScript = `
async function testHttpsRequest() {
try {
const response = await fetch('https://httpbin.org/user-agent');
console.log('SUCCESS: GitHub request completed with status', response.status);
process.exit(0);
} catch (error) {
console.log('ERROR: HTTPS request failed:', error.message);
process.exit(1);
}
}
testHttpsRequest();
`;
await fs.writeFile(join(testDir, "test.js"), testScript);
// Test with NODE_USE_SYSTEM_CA=1
const proc1 = Bun.spawn({
cmd: [bunExe(), "test.js"],
env: {
...bunEnv,
NODE_USE_SYSTEM_CA: "1",
},
cwd: testDir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout1, stderr1, exitCode1] = await Promise.all([proc1.stdout.text(), proc1.stderr.text(), proc1.exited]);
expect(exitCode1).toBe(0);
expect(stdout1).toContain("SUCCESS");
// Test without NODE_USE_SYSTEM_CA
const proc2 = Bun.spawn({
cmd: [bunExe(), "test.js"],
env: bunEnv,
cwd: testDir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout2, stderr2, exitCode2] = await Promise.all([proc2.stdout.text(), proc2.stderr.text(), proc2.exited]);
expect(exitCode2).toBe(0);
expect(stdout2).toContain("SUCCESS");
});
const networkTest = process.env.BUN_TEST_ALLOW_NET === "1" ? test : test.skip;
describe("NODE_USE_SYSTEM_CA Complete Implementation", () => {
networkTest("should work with standard HTTPS sites", async () => {
const testDir = tempDirWithFiles("node-use-system-ca-basic", {});
const testScript = `
async function testHttpsRequest() {
try {
const response = await fetch('https://httpbin.org/user-agent');
console.log('SUCCESS: HTTPS request completed with status', response.status);
process.exit(0);
} catch (error) {
console.log('ERROR: HTTPS request failed:', error.message);
process.exit(1);
}
}
testHttpsRequest();
`;
await fs.writeFile(join(testDir, "test.js"), testScript);
// Test with NODE_USE_SYSTEM_CA=1
const proc1 = Bun.spawn({
cmd: [bunExe(), "test.js"],
env: {
...bunEnv,
NODE_USE_SYSTEM_CA: "1",
},
cwd: testDir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout1, stderr1, exitCode1] = await Promise.all([proc1.stdout.text(), proc1.stderr.text(), proc1.exited]);
expect(exitCode1).toBe(0);
expect(stdout1).toContain("SUCCESS");
// Test without NODE_USE_SYSTEM_CA
const proc2 = Bun.spawn({
cmd: [bunExe(), "test.js"],
env: bunEnv,
cwd: testDir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout2, stderr2, exitCode2] = await Promise.all([proc2.stdout.text(), proc2.stderr.text(), proc2.exited]);
expect(exitCode2).toBe(0);
expect(stdout2).toContain("SUCCESS");
});
🤖 Prompt for AI Agents
In test/js/bun/fetch/node-use-system-ca-complete.test.ts around lines 7 to 58,
the networked test should be gated behind the BUN_TEST_ALLOW_NET flag and the
success log message is misleading (mentions "GitHub" while calling httpbin.org);
update the test to early-skip when process.env.BUN_TEST_ALLOW_NET is not truthy
(or use the existing test skip helper) so CI won’t run external network calls,
and change the console.log success message in the test script to reference
httpbin.org (or a generic "HTTPS request") instead of "GitHub" so logs
accurately reflect the endpoint being called.

Comment on lines +136 to +255
networkTest(
"should work with Bun.serve and fetch using system certificates",
async () => {
const testDir = tempDirWithFiles("node-use-system-ca-serve", {});

const serverScript = `
const server = Bun.serve({
port: 0,
fetch(req) {
return new Response('Hello from test server');
},
});

console.log(\`Server listening on port \${server.port}\`);

// Keep server alive
await new Promise(() => {}); // Never resolves
`;

const clientScript = `
const port = process.argv[2];

async function testClient() {
try {
// Test local HTTP first (should work)
const response = await fetch(\`http://localhost:\${port}\`);
const text = await response.text();
console.log('Local HTTP request successful:', text);

// Test external HTTPS with system CA
const httpsResponse = await fetch('https://httpbin.org/get');
console.log('External HTTPS request successful');

process.exit(0);
} catch (error) {
console.error('Client request failed:', error.message);
process.exit(1);
}
}

testClient();
`;

await fs.writeFile(join(testDir, "server.js"), serverScript);
await fs.writeFile(join(testDir, "client.js"), clientScript);

// Start server
const serverProc = Bun.spawn({
cmd: [bunExe(), "server.js"],
env: {
...bunEnv,
NODE_USE_SYSTEM_CA: "1",
},
cwd: testDir,
stdout: "pipe",
stderr: "pipe",
});

// Wait for server to start and get port
let serverPort;
const serverOutput = [];
const reader = serverProc.stdout.getReader();

const timeout = setTimeout(() => {
serverProc.kill();
}, 10000);

try {
while (true) {
const { done, value } = await reader.read();
if (done) break;

const chunk = new TextDecoder().decode(value);
serverOutput.push(chunk);

const match = chunk.match(/Server listening on port (\d+)/);
if (match) {
serverPort = match[1];
break;
}
}
} finally {
reader.releaseLock();
}

expect(serverPort).toBeDefined();
console.log("Server started on port:", serverPort);

// Test client
const clientProc = Bun.spawn({
cmd: [bunExe(), "client.js", serverPort],
env: {
...bunEnv,
NODE_USE_SYSTEM_CA: "1",
},
cwd: testDir,
stdout: "pipe",
stderr: "pipe",
});

const [clientStdout, clientStderr, clientExitCode] = await Promise.all([
clientProc.stdout.text(),
clientProc.stderr.text(),
clientProc.exited,
]);

// Clean up server
clearTimeout(timeout);
serverProc.kill();

console.log("Client output:", clientStdout);
console.log("Client errors:", clientStderr);

expect(clientExitCode).toBe(0);
expect(clientStdout).toContain("Local HTTP request successful");
expect(clientStdout).toContain("External HTTPS request successful");
},
30000,
); // 30 second timeout for this test
});

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.

⚠️ Potential issue

Remove explicit test timeout and ensure cleanup with await using

  • Don’t set explicit test timeouts; rely on Bun’s built-in timeouts.
  • Use await using for the server process to guarantee cleanup even on failures.
-      const serverProc = Bun.spawn({
+      await using serverProc = Bun.spawn({
         cmd: [bunExe(), "server.js"],
         env: {
           ...bunEnv,
           NODE_USE_SYSTEM_CA: "1",
         },
         cwd: testDir,
         stdout: "pipe",
         stderr: "pipe",
       });
@@
-    30000,
-  ); // 30 second timeout for this test
+  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
networkTest(
"should work with Bun.serve and fetch using system certificates",
async () => {
const testDir = tempDirWithFiles("node-use-system-ca-serve", {});
const serverScript = `
const server = Bun.serve({
port: 0,
fetch(req) {
return new Response('Hello from test server');
},
});
console.log(\`Server listening on port \${server.port}\`);
// Keep server alive
await new Promise(() => {}); // Never resolves
`;
const clientScript = `
const port = process.argv[2];
async function testClient() {
try {
// Test local HTTP first (should work)
const response = await fetch(\`http://localhost:\${port}\`);
const text = await response.text();
console.log('Local HTTP request successful:', text);
// Test external HTTPS with system CA
const httpsResponse = await fetch('https://httpbin.org/get');
console.log('External HTTPS request successful');
process.exit(0);
} catch (error) {
console.error('Client request failed:', error.message);
process.exit(1);
}
}
testClient();
`;
await fs.writeFile(join(testDir, "server.js"), serverScript);
await fs.writeFile(join(testDir, "client.js"), clientScript);
// Start server
const serverProc = Bun.spawn({
cmd: [bunExe(), "server.js"],
env: {
...bunEnv,
NODE_USE_SYSTEM_CA: "1",
},
cwd: testDir,
stdout: "pipe",
stderr: "pipe",
});
// Wait for server to start and get port
let serverPort;
const serverOutput = [];
const reader = serverProc.stdout.getReader();
const timeout = setTimeout(() => {
serverProc.kill();
}, 10000);
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = new TextDecoder().decode(value);
serverOutput.push(chunk);
const match = chunk.match(/Server listening on port (\d+)/);
if (match) {
serverPort = match[1];
break;
}
}
} finally {
reader.releaseLock();
}
expect(serverPort).toBeDefined();
console.log("Server started on port:", serverPort);
// Test client
const clientProc = Bun.spawn({
cmd: [bunExe(), "client.js", serverPort],
env: {
...bunEnv,
NODE_USE_SYSTEM_CA: "1",
},
cwd: testDir,
stdout: "pipe",
stderr: "pipe",
});
const [clientStdout, clientStderr, clientExitCode] = await Promise.all([
clientProc.stdout.text(),
clientProc.stderr.text(),
clientProc.exited,
]);
// Clean up server
clearTimeout(timeout);
serverProc.kill();
console.log("Client output:", clientStdout);
console.log("Client errors:", clientStderr);
expect(clientExitCode).toBe(0);
expect(clientStdout).toContain("Local HTTP request successful");
expect(clientStdout).toContain("External HTTPS request successful");
},
30000,
); // 30 second timeout for this test
});
networkTest(
"should work with Bun.serve and fetch using system certificates",
async () => {
const testDir = tempDirWithFiles("node-use-system-ca-serve", {});
const serverScript = `
const server = Bun.serve({
port: 0,
fetch(req) {
return new Response('Hello from test server');
},
});
console.log(\`Server listening on port \${server.port}\`);
// Keep server alive
await new Promise(() => {}); // Never resolves
`;
const clientScript = `
const port = process.argv[2];
async function testClient() {
try {
// Test local HTTP first (should work)
const response = await fetch(\`http://localhost:\${port}\`);
const text = await response.text();
console.log('Local HTTP request successful:', text);
// Test external HTTPS with system CA
const httpsResponse = await fetch('https://httpbin.org/get');
console.log('External HTTPS request successful');
process.exit(0);
} catch (error) {
console.error('Client request failed:', error.message);
process.exit(1);
}
}
testClient();
`;
await fs.writeFile(join(testDir, "server.js"), serverScript);
await fs.writeFile(join(testDir, "client.js"), clientScript);
// Start server
await using serverProc = Bun.spawn({
cmd: [bunExe(), "server.js"],
env: {
...bunEnv,
NODE_USE_SYSTEM_CA: "1",
},
cwd: testDir,
stdout: "pipe",
stderr: "pipe",
});
// Wait for server to start and get port
let serverPort;
const serverOutput = [];
const reader = serverProc.stdout.getReader();
const timeout = setTimeout(() => {
serverProc.kill();
}, 10000);
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = new TextDecoder().decode(value);
serverOutput.push(chunk);
const match = chunk.match(/Server listening on port (\d+)/);
if (match) {
serverPort = match[1];
break;
}
}
} finally {
reader.releaseLock();
}
expect(serverPort).toBeDefined();
console.log("Server started on port:", serverPort);
// Test client
const clientProc = Bun.spawn({
cmd: [bunExe(), "client.js", serverPort],
env: {
...bunEnv,
NODE_USE_SYSTEM_CA: "1",
},
cwd: testDir,
stdout: "pipe",
stderr: "pipe",
});
const [clientStdout, clientStderr, clientExitCode] = await Promise.all([
clientProc.stdout.text(),
clientProc.stderr.text(),
clientProc.exited,
]);
// Clean up server
clearTimeout(timeout);
serverProc.kill();
console.log("Client output:", clientStdout);
console.log("Client errors:", clientStderr);
expect(clientExitCode).toBe(0);
expect(clientStdout).toContain("Local HTTP request successful");
expect(clientStdout).toContain("External HTTPS request successful");
},
);
});
🤖 Prompt for AI Agents
In test/js/bun/fetch/node-use-system-ca.test.ts around lines 136-255, remove the
explicit 30000 test timeout and replace manual process cleanup with RAII-style
disposal: delete the trailing ", 30000" timeout on networkTest, remove the
timeout timer and the manual serverProc.kill()/clearTimeout() cleanup, and
create both serverProc and clientProc using "await using" so Bun spawns are
automatically cleaned up on return/exception; keep the server stdout reading
logic and assertions but ensure reader.releaseLock() still runs before scope
exit so the await using disposal can terminate the processes reliably.

Comment on lines +16 to +22
const { child: { stdout: expectedLength } } = spawnSyncAndExitWithoutError(process.execPath, [
'--use-system-ca',
'-p',
`tls.getCACertificates('default').length`,
], {
env: { ...process.env, NODE_USE_SYSTEM_CA: '0' },
});

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.

⚠️ Potential issue

Trim/parse stdout before embedding into eval string

expectedLength.toString() includes a newline; embedding it directly can break the -p eval. Parse/trim first.

Apply this diff:

-const { child: { stdout: expectedLength } } = spawnSyncAndExitWithoutError(process.execPath, [
+const { child: { stdout: expectedLengthBuf } } = spawnSyncAndExitWithoutError(process.execPath, [
   '--use-system-ca',
   '-p',
   `tls.getCACertificates('default').length`,
 ], {
   env: { ...process.env, NODE_USE_SYSTEM_CA: '0' },
 });
+const expectedLength = Number(String(expectedLengthBuf).trim());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { child: { stdout: expectedLength } } = spawnSyncAndExitWithoutError(process.execPath, [
'--use-system-ca',
'-p',
`tls.getCACertificates('default').length`,
], {
env: { ...process.env, NODE_USE_SYSTEM_CA: '0' },
});
const { child: { stdout: expectedLengthBuf } } = spawnSyncAndExitWithoutError(process.execPath, [
'--use-system-ca',
'-p',
`tls.getCACertificates('default').length`,
], {
env: { ...process.env, NODE_USE_SYSTEM_CA: '0' },
});
const expectedLength = Number(String(expectedLengthBuf).trim());
🤖 Prompt for AI Agents
In test/js/node/test/parallel/test-tls-get-ca-certificates-node-use-system-ca.js
around lines 16 to 22, the stdout value used in the spawned -p eval may contain
a trailing newline which can break the eval; trim or parse the stdout before
embedding it into the eval string (e.g. call expectedLength.toString().trim() or
parseInt(expectedLength, 10) and use that trimmed/parsed value when building the
'-p' argument) so the embedded expression is clean.

Comment thread test/js/node/test/system-ca/test-native-root-certs.test.mjs Outdated
Comment on lines +6 to +39
describe("NODE_EXTRA_CA_CERTS", () => {
test("loads additional certificates from file", async () => {
// Create a test certificate file
const testCert = `-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKLdQVPy90WjMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTgwNDEwMDgwNzQ4WhcNMjgwNDA3MDgwNzQ4WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAyOB7tY2Uo2lTNjJgGEhJAVZDWnHbLjbmTMP4pSXLlNMr9KdyaKE+J3xn
xAz7TbGPHUBH5dqMzlWqEkZxcY9u9GL19SJPpC7dl8K8V5dKBwvgOubcLp4qLvZU
-----END CERTIFICATE-----`;

const dir = tempDirWithFiles("test-extra-ca", {
"extra-ca.pem": testCert,
"test.js": `console.log('OK');`,
});

const certPath = join(dir, "extra-ca.pem");

// Test that NODE_EXTRA_CA_CERTS loads the certificate
await using proc = spawn({
cmd: [bunExe(), "test.js"],
env: { ...bunEnv, NODE_EXTRA_CA_CERTS: certPath },
cwd: dir,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(exitCode).toBe(0);
expect(stdout.trim()).toBe("OK");
});

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.

⚠️ Potential issue

Tests don’t exercise NODE_EXTRA_CA_CERTS code path

The child script never calls tls.getCACertificates('extra' | 'default'), so the env var is unused. Assert via a child that queries TLS CAs.

Apply this diff:

-  test("loads additional certificates from file", async () => {
+  test("does not crash and exposes extra CAs array", async () => {
@@
-    const dir = tempDirWithFiles("test-extra-ca", {
-      "extra-ca.pem": testCert,
-      "test.js": `console.log('OK');`,
-    });
+    const dir = tempDirWithFiles("test-extra-ca", {
+      "extra-ca.pem": testCert,
+      "test.js": `
+        const tls = require('tls');
+        // Access both to ensure code path executes even if extra is empty/invalid.
+        console.log(JSON.stringify({
+          def: tls.getCACertificates('default').length,
+          extra: tls.getCACertificates('extra').length
+        }));
+      `,
+    });
@@
-    expect(exitCode).toBe(0);
-    expect(stdout.trim()).toBe("OK");
+    expect(exitCode).toBe(0);
+    const out = JSON.parse(stdout.trim());
+    expect(typeof out.def).toBe("number");
+    expect(typeof out.extra).toBe("number");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
describe("NODE_EXTRA_CA_CERTS", () => {
test("loads additional certificates from file", async () => {
// Create a test certificate file
const testCert = `-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKLdQVPy90WjMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTgwNDEwMDgwNzQ4WhcNMjgwNDA3MDgwNzQ4WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAyOB7tY2Uo2lTNjJgGEhJAVZDWnHbLjbmTMP4pSXLlNMr9KdyaKE+J3xn
xAz7TbGPHUBH5dqMzlWqEkZxcY9u9GL19SJPpC7dl8K8V5dKBwvgOubcLp4qLvZU
-----END CERTIFICATE-----`;
const dir = tempDirWithFiles("test-extra-ca", {
"extra-ca.pem": testCert,
"test.js": `console.log('OK');`,
});
const certPath = join(dir, "extra-ca.pem");
// Test that NODE_EXTRA_CA_CERTS loads the certificate
await using proc = spawn({
cmd: [bunExe(), "test.js"],
env: { ...bunEnv, NODE_EXTRA_CA_CERTS: certPath },
cwd: dir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode).toBe(0);
expect(stdout.trim()).toBe("OK");
});
describe("NODE_EXTRA_CA_CERTS", () => {
test("does not crash and exposes extra CAs array", async () => {
// Create a test certificate file
const testCert = `-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKLdQVPy90WjMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTgwNDEwMDgwNzQ4WhcNMjgwNDA3MDgwNzQ4WjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAyOB7tY2Uo2lTNjJgGEhJAVZDWnHbLjbmTMP4pSXLlNMr9KdyaKE+J3xn
xAz7TbGPHUBH5dqMzlWqEkZxcY9u9GL19SJPpC7dl8K8V5dKBwvgOubcLp4qLvZU
-----END CERTIFICATE-----`;
const dir = tempDirWithFiles("test-extra-ca", {
"extra-ca.pem": testCert,
"test.js": `
const tls = require('tls');
// Access both to ensure code path executes even if extra is empty/invalid.
console.log(JSON.stringify({
def: tls.getCACertificates('default').length,
extra: tls.getCACertificates('extra').length
}));
`,
});
const certPath = join(dir, "extra-ca.pem");
// Test that NODE_EXTRA_CA_CERTS loads the certificate
await using proc = spawn({
cmd: [bunExe(), "test.js"],
env: { ...bunEnv, NODE_EXTRA_CA_CERTS: certPath },
cwd: dir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode).toBe(0);
const out = JSON.parse(stdout.trim());
expect(typeof out.def).toBe("number");
expect(typeof out.extra).toBe("number");
});
🤖 Prompt for AI Agents
In test/js/node/tls/test-node-extra-ca-certs.test.ts around lines 6–39, the test
spawns a child that only prints "OK" so NODE_EXTRA_CA_CERTS is never exercised;
update the child script (test.js) to require('tls') and call
tls.getCACertificates('extra') (or 'default' as appropriate) and print or
JSON.stringify the returned cert list so the parent can assert the extra CA is
present, then change the parent test to parse the child's stdout and assert that
the returned certificate array contains the PEM or expected fingerprint of
testCert (and keep the existing exitCode/assertions).

@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

🧹 Nitpick comments (1)
src/cli/Arguments.zig (1)

774-779: Use bun.getenvTruthy for NODE_USE_SYSTEM_CA
Node.js treats any non-empty value as truthy, so bun.getenvTruthy will accept both “1” and “true” and simplifies the logic:

-            if (bun.getenvZ("NODE_USE_SYSTEM_CA")) |val| {
-                if (val.len > 0 and val[0] == '1') {
-                    Bun__Node__CAStore = .system;
-                }
-            }
+            if (bun.getenvTruthy("NODE_USE_SYSTEM_CA")) {
+                Bun__Node__CAStore = .system;
+            }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 40d2637 and 328f1e5.

📒 Files selected for processing (3)
  • src/bun.zig (1 hunks)
  • src/cli/Arguments.zig (3 hunks)
  • test/no-validate-exceptions.txt (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/no-validate-exceptions.txt
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/cli/Arguments.zig
  • src/bun.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

**/*.zig: Format Zig files with zig-format (bun run zig-format)
In Zig, manage memory carefully with allocators and use defer for cleanup

Files:

  • src/cli/Arguments.zig
  • src/bun.zig
🧠 Learnings (8)
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/{**/js_*.zig,bun.js/api/**/*.zig} : Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Applied to files:

  • src/bun.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/bun.zig
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Applied to files:

  • src/bun.zig
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : Add new V8 API method mangled symbols to the V8API struct in src/napi/napi.zig for both GCC/Clang and MSVC

Applied to files:

  • src/bun.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)

Applied to files:

  • src/bun.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/bun.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/bun.js/api/BunObject.zig : Create a getter function in BunObject.zig (define const Getter with toJSGetter and export it in exportAll)

Applied to files:

  • src/bun.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions

Applied to files:

  • src/bun.zig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (3)
src/cli/Arguments.zig (3)

782-783: Back-compat boolean is fine.

Setting Bun__Node__UseSystemCA from the enum centralizes state nicely; callers have an easy boolean while native code migrates.


1288-1291: Public CA store surface looks good; stable ABI.

Enum underlying type specified (u8) and exported vars provide a stable ABI for native bindings. Good defaults.


107-109: CLI flags parity with Node.js confirmed — the --use-system-ca, --use-openssl-ca, and --use-bundled-ca flags match Node.js names and semantics.

Comment thread src/bun.zig
Comment on lines +3785 to +3791
// Export function to check if --use-system-ca flag is set
pub fn getUseSystemCA(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) error{ JSError, OutOfMemory }!jsc.JSValue {
_ = globalObject;
_ = callFrame;
const Arguments = @import("./cli/Arguments.zig");
return jsc.JSValue.jsBoolean(Arguments.Bun__Node__UseSystemCA);
}

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.

🛠️ Refactor suggestion

🧩 Analysis chain

Align JS-binding signature with house style (bun.JSError).

Use bun.JSError!JSC.JSValue for JS-exposed functions per project conventions. Also ensure this is exported via the intended binding mechanism (codegen or explicit export) so it’s not DCE’d.

Apply this diff:

-pub fn getUseSystemCA(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) error{ JSError, OutOfMemory }!jsc.JSValue {
+pub fn getUseSystemCA(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!jsc.JSValue {

And verify the binding/export:


🏁 Script executed:

#!/bin/bash
# Find references and exports for getUseSystemCA
rg -n -C3 '\bgetUseSystemCA\b'
rg -n -C3 'ZIG_EXPORT|extern|export var|export fn' --type=zig

Length of output: 268259


Use bun.JSError!jsc.JSValue and export getUseSystemCA
In src/bun.zig at the getUseSystemCA definition, change to:

pub export fn getUseSystemCA(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame) bun.JSError!jsc.JSValue {
    …
}

to follow the bun.JSError!JSC.JSValue convention and ensure it’s emitted for $newZigFunction in src/js/node/tls.ts.

🤖 Prompt for AI Agents
In src/bun.zig around lines 3785 to 3791, the function signature for
getUseSystemCA must be changed to export the symbol and use the
bun.JSError!jsc.JSValue error-return convention required by $newZigFunction
consumers; replace the current signature with "pub export fn
getUseSystemCA(globalObject: *jsc.JSGlobalObject, callFrame: *jsc.CallFrame)
bun.JSError!jsc.JSValue" (keeping the same body/return) so the function is
emitted with the correct error type for src/js/node/tls.ts.

Comment thread src/cli/Arguments.zig
Comment on lines +760 to +764
// Disallow any combination > 1
if (@as(u8, @intFromBool(use_system_ca)) + @as(u8, @intFromBool(use_openssl_ca)) + @as(u8, @intFromBool(use_bundled_ca)) > 1) {
Output.prettyErrorln("<r><red>error<r>: choose exactly one of --use-system-ca, --use-openssl-ca, or --use-bundled-ca", .{});
Global.exit(1);
}

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.

⚠️ Potential issue

Error message mismatches behavior (allows zero).

You allow “at most one”, but the message says “exactly one”. Tweak the wording to avoid confusing users.

Apply this diff:

-            Output.prettyErrorln("<r><red>error<r>: choose exactly one of --use-system-ca, --use-openssl-ca, or --use-bundled-ca", .{});
+            Output.prettyErrorln("<r><red>error<r>: cannot combine --use-system-ca, --use-openssl-ca, and --use-bundled-ca; pick at most one", .{});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Disallow any combination > 1
if (@as(u8, @intFromBool(use_system_ca)) + @as(u8, @intFromBool(use_openssl_ca)) + @as(u8, @intFromBool(use_bundled_ca)) > 1) {
Output.prettyErrorln("<r><red>error<r>: choose exactly one of --use-system-ca, --use-openssl-ca, or --use-bundled-ca", .{});
Global.exit(1);
}
// Disallow any combination > 1
if (@as(u8, @intFromBool(use_system_ca))
@as(u8, @intFromBool(use_openssl_ca))
@as(u8, @intFromBool(use_bundled_ca)) > 1)
{
Output.prettyErrorln(
"<r><red>error<r>: cannot combine --use-system-ca, --use-openssl-ca, and --use-bundled-ca; pick at most one",
.{},
);
Global.exit(1);
}
🤖 Prompt for AI Agents
In src/cli/Arguments.zig around lines 760 to 764, the code enforces "at most
one" selection but the error message says "choose exactly one"; update the error
text to reflect the actual behavior (e.g., "error: choose at most one of
--use-system-ca, --use-openssl-ca, or --use-bundled-ca") so the message matches
the check that forbids more than one selection.

@Jarred-Sumner Jarred-Sumner merged commit 7798e66 into main Sep 25, 2025
59 of 62 checks passed
@Jarred-Sumner Jarred-Sumner deleted the ciro/claude-system-ca branch September 25, 2025 04:55
cirospaciari added a commit that referenced this pull request May 8, 2026
…#30408)

### What does this PR do?

Brings `--use-system-ca` / `NODE_USE_SYSTEM_CA` on Windows to parity
with Node.js's `ReadWindowsCertificates`
(`src/crypto/crypto_context.cc`).

Before this change, `root_certs_windows.cpp` only enumerated the `ROOT`
store for `CURRENT_USER` and `LOCAL_MACHINE` (2 `CertOpenStore` calls).
Node opens 13: `ROOT`, `CA` (intermediates), and `TrustedPeople` across
`LOCAL_MACHINE`, `CURRENT_USER`, and the group-policy / enterprise
variants — and filters by EKU.

The most user-visible consequence of the old behavior: when a server is
misconfigured and sends only the leaf cert without its intermediates
(very common on intranets, the primary use case for `--use-system-ca`),
Node can still build the chain from the intermediates Windows keeps in
the `CA` store; Bun would fail with `unable to get local issuer
certificate`.

Changes, all mirroring Node:

| | before | after |
|---|---|---|
| Store names | `ROOT` | `ROOT`, `CA`, `TrustedPeople` |
| Locations | `LOCAL_MACHINE`, `CURRENT_USER` | + `GROUP_POLICY`,
`ENTERPRISE` variants |
| `CERT_STORE_OPEN_EXISTING_FLAG` | no | yes (don't create a missing
store) |
| EKU server-auth filter (`CertGetEnhancedKeyUsage`) | no | yes (skip
certs restricted to e.g. code-signing only) |

`IsCertTrustedForServerAuth` and `GatherCertsForLocation` are direct
ports of the equivalents in Node's `crypto_context.cc`, adapted to Bun's
raw-DER-blob layering (this TU is kept OpenSSL-free to avoid
`wincrypt.h` / BoringSSL macro collisions; `root_certs.cpp` does the
`d2i_X509` conversion).


### Related issues (context, not fixes)

The issue-finder bot flagged #17108, #28612, and #9365. None of them are
closed by this PR because it only changes behavior when
`--use-system-ca` / `NODE_USE_SYSTEM_CA=1` is set:

- #17108 asked for the base feature, which #22441 already shipped — this
PR refines which Windows stores it reads.
- #28612 reports `unable to get local issuer certificate` with **no**
`--use-system-ca` set (default bundled store, public CAs, TTY-startup
race) — different layer.
- #9365 reproduces on WSL/Linux too and predates `--use-system-ca` —
likely a server omitting intermediates with no system-store fallback at
all.
springmin pushed a commit to springmin/bun that referenced this pull request May 10, 2026
…oven-sh#30408)

### What does this PR do?

Brings `--use-system-ca` / `NODE_USE_SYSTEM_CA` on Windows to parity
with Node.js's `ReadWindowsCertificates`
(`src/crypto/crypto_context.cc`).

Before this change, `root_certs_windows.cpp` only enumerated the `ROOT`
store for `CURRENT_USER` and `LOCAL_MACHINE` (2 `CertOpenStore` calls).
Node opens 13: `ROOT`, `CA` (intermediates), and `TrustedPeople` across
`LOCAL_MACHINE`, `CURRENT_USER`, and the group-policy / enterprise
variants — and filters by EKU.

The most user-visible consequence of the old behavior: when a server is
misconfigured and sends only the leaf cert without its intermediates
(very common on intranets, the primary use case for `--use-system-ca`),
Node can still build the chain from the intermediates Windows keeps in
the `CA` store; Bun would fail with `unable to get local issuer
certificate`.

Changes, all mirroring Node:

| | before | after |
|---|---|---|
| Store names | `ROOT` | `ROOT`, `CA`, `TrustedPeople` |
| Locations | `LOCAL_MACHINE`, `CURRENT_USER` | + `GROUP_POLICY`,
`ENTERPRISE` variants |
| `CERT_STORE_OPEN_EXISTING_FLAG` | no | yes (don't create a missing
store) |
| EKU server-auth filter (`CertGetEnhancedKeyUsage`) | no | yes (skip
certs restricted to e.g. code-signing only) |

`IsCertTrustedForServerAuth` and `GatherCertsForLocation` are direct
ports of the equivalents in Node's `crypto_context.cc`, adapted to Bun's
raw-DER-blob layering (this TU is kept OpenSSL-free to avoid
`wincrypt.h` / BoringSSL macro collisions; `root_certs.cpp` does the
`d2i_X509` conversion).

### Related issues (context, not fixes)

The issue-finder bot flagged oven-sh#17108, oven-sh#28612, and oven-sh#9365. None of them are
closed by this PR because it only changes behavior when
`--use-system-ca` / `NODE_USE_SYSTEM_CA=1` is set:

- oven-sh#17108 asked for the base feature, which oven-sh#22441 already shipped — this
PR refines which Windows stores it reads.
- oven-sh#28612 reports `unable to get local issuer certificate` with **no**
`--use-system-ca` set (default bundled store, public CAs, TTY-startup
race) — different layer.
- oven-sh#9365 reproduces on WSL/Linux too and predates `--use-system-ca` —
likely a server omitting intermediates with no system-store fallback at
all.

Signed-off-by: Sisyphus <sisyphus@ohos-bun.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants