Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
444407095e fix(tls): getCACertificates('system') now works without --use-system-ca
Previously, `tls.getCACertificates('system')` returned an empty array
unless the `--use-system-ca` flag or `NODE_USE_SYSTEM_CA=1` environment
variable was set. According to Node.js semantics, the 'system' type
should always return system CA certificates regardless of these flags.

The fix unconditionally loads system certificates during initialization.
The `--use-system-ca` flag still controls whether system certs are
included in the 'default' certificate list.

Fixes #24339

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 22:25:56 +00:00
2 changed files with 133 additions and 6 deletions

View File

@@ -161,16 +161,16 @@ static void us_internal_init_root_certs(
root_extra_cert_instances = us_ssl_ctx_load_all_certs_from_file(extra_certs);
}
// load system certificates if NODE_USE_SYSTEM_CA=1
if (us_should_use_system_ca()) {
// Always load system certificates so getCACertificates('system') works
// regardless of NODE_USE_SYSTEM_CA flag. The flag only controls whether
// system certs are included in the 'default' certificate list.
#ifdef __APPLE__
us_load_system_certificates_macos(&root_system_cert_instances);
us_load_system_certificates_macos(&root_system_cert_instances);
#elif defined(_WIN32)
us_load_system_certificates_windows(&root_system_cert_instances);
us_load_system_certificates_windows(&root_system_cert_instances);
#else
us_load_system_certificates_linux(&root_system_cert_instances);
us_load_system_certificates_linux(&root_system_cert_instances);
#endif
}
}
atomic_flag_clear_explicit(&root_cert_instances_lock,

View File

@@ -0,0 +1,127 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
// Regression test for https://github.com/oven-sh/bun/issues/24339
// tls.getCACertificates('system') should return system certificates
// regardless of --use-system-ca flag or NODE_USE_SYSTEM_CA env var
test("getCACertificates('system') returns system certs without --use-system-ca", async () => {
// Run without any flags - should still return system certificates
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", "console.log(JSON.stringify(require('tls').getCACertificates('system').length))"],
env: {
...bunEnv,
// Explicitly unset to ensure we're testing the default behavior
NODE_USE_SYSTEM_CA: undefined,
},
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
const count = JSON.parse(stdout.trim());
// System should have at least some CA certificates installed
expect(count).toBeGreaterThan(0);
expect(exitCode).toBe(0);
});
test("getCACertificates('system') returns same certs with and without --use-system-ca", async () => {
// Get system certs without the flag
await using proc1 = Bun.spawn({
cmd: [bunExe(), "-e", "console.log(JSON.stringify(require('tls').getCACertificates('system').length))"],
env: {
...bunEnv,
NODE_USE_SYSTEM_CA: undefined,
},
stdout: "pipe",
stderr: "pipe",
});
// Get system certs with the flag
await using proc2 = Bun.spawn({
cmd: [
bunExe(),
"--use-system-ca",
"-e",
"console.log(JSON.stringify(require('tls').getCACertificates('system').length))",
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout1, stderr1, exitCode1] = await Promise.all([proc1.stdout.text(), proc1.stderr.text(), proc1.exited]);
const [stdout2, stderr2, exitCode2] = await Promise.all([proc2.stdout.text(), proc2.stderr.text(), proc2.exited]);
expect(stderr1).toBe("");
expect(stderr2).toBe("");
const countWithoutFlag = JSON.parse(stdout1.trim());
const countWithFlag = JSON.parse(stdout2.trim());
// Both should return the same number of system certificates
expect(countWithoutFlag).toBe(countWithFlag);
expect(countWithoutFlag).toBeGreaterThan(0);
expect(exitCode1).toBe(0);
expect(exitCode2).toBe(0);
});
test("getCACertificates('default') only includes system certs with --use-system-ca", async () => {
// Get default certs without the flag
await using proc1 = Bun.spawn({
cmd: [bunExe(), "-e", "console.log(JSON.stringify(require('tls').getCACertificates('default').length))"],
env: {
...bunEnv,
NODE_USE_SYSTEM_CA: undefined,
},
stdout: "pipe",
stderr: "pipe",
});
// Get default certs with the flag
await using proc2 = Bun.spawn({
cmd: [
bunExe(),
"--use-system-ca",
"-e",
"console.log(JSON.stringify(require('tls').getCACertificates('default').length))",
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
// Get default certs with NODE_USE_SYSTEM_CA=1 env var
await using proc3 = Bun.spawn({
cmd: [bunExe(), "-e", "console.log(JSON.stringify(require('tls').getCACertificates('default').length))"],
env: {
...bunEnv,
NODE_USE_SYSTEM_CA: "1",
},
stdout: "pipe",
stderr: "pipe",
});
const [stdout1, stderr1, exitCode1] = await Promise.all([proc1.stdout.text(), proc1.stderr.text(), proc1.exited]);
const [stdout2, stderr2, exitCode2] = await Promise.all([proc2.stdout.text(), proc2.stderr.text(), proc2.exited]);
const [stdout3, stderr3, exitCode3] = await Promise.all([proc3.stdout.text(), proc3.stderr.text(), proc3.exited]);
expect(stderr1).toBe("");
expect(stderr2).toBe("");
expect(stderr3).toBe("");
const countWithoutFlag = JSON.parse(stdout1.trim());
const countWithFlag = JSON.parse(stdout2.trim());
const countWithEnv = JSON.parse(stdout3.trim());
// With --use-system-ca, default should include system certs (more certificates)
expect(countWithFlag).toBeGreaterThan(countWithoutFlag);
// NODE_USE_SYSTEM_CA=1 should behave the same as --use-system-ca
expect(countWithEnv).toBe(countWithFlag);
expect(exitCode1).toBe(0);
expect(exitCode2).toBe(0);
expect(exitCode3).toBe(0);
});