Skip to content

fix(ext/node): add tls.setDefaultCACertificates support#31522

Merged
bartlomieju merged 10 commits intodenoland:mainfrom
TarikSogukpinar:feat/tls-set-default-ca-certificates
Jan 9, 2026
Merged

fix(ext/node): add tls.setDefaultCACertificates support#31522
bartlomieju merged 10 commits intodenoland:mainfrom
TarikSogukpinar:feat/tls-set-default-ca-certificates

Conversation

@TarikSogukpinar
Copy link
Copy Markdown
Contributor

feat(node): implement tls.setDefaultCACertificates

related issue:
closes # 31208

The tls.setDefaultCACertificates API was missing in node:tls, causing TypeError in applications that rely on programmatically overriding the global CA store (e.g., for custom root CAs or testing environments).

How
• Added NodeTlsState to OpState in Rust to store custom certificates per-isolate, ensuring proper worker isolation and thread safety
• Updated op_get_root_certificates to check for custom certificates in OpState before falling back to system roots
• Implemented tls.setDefaultCACertificates in the polyfill to update the native state
• Added cache invalidation logic for lazyRootCertificates to ensure subsequent calls fetch the updated list
• Implemented in-place array clearing (target.length = 0) for the rootCertificates proxy to preserve object references while updating data

Copilot AI review requested due to automatic review settings December 7, 2025 15:19
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 7, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds op_set_default_ca_certificates to the deno_node extension and a NodeTlsState stored in OpState to hold optional custom CA certificates. op_get_root_certificates now accepts a mutable OpState and returns custom certificates if present or default PEM-formatted root certificates otherwise. Adds op_set_default_ca_certificates to store provided certs. The Node TLS polyfill gains setDefaultCACertificates(certs: string[]) with input validation and resets the lazyRootCertificates cache. Unit tests were added for presence, input validation, and acceptance of valid certificates.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main feature being added: tls.setDefaultCACertificates support in the Node compatibility layer.
Description check ✅ Passed The description is well-related to the changeset, explaining the motivation, implementation approach, and key technical details across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f28ef7 and 4c03231.

📒 Files selected for processing (2)
  • ext/node/ops/tls.rs (2 hunks)
  • tests/unit_node/tls_test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js}: For JavaScript runtime debugging, enable V8 inspector with --inspect-brk flag and connect Chrome DevTools to chrome://inspect
Use console.log() for debug prints in JavaScript runtime code

Files:

  • tests/unit_node/tls_test.ts
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: For debugging Rust code, set breakpoints in IDE debuggers (VS Code with rust-analyzer, IntelliJ IDEA) or use lldb directly
Use eprintln!() or dbg!() macros for debug prints in Rust code

Files:

  • ext/node/ops/tls.rs

⚙️ CodeRabbit configuration file

Don't worry about coverage of Rust docstrings. Don't be nitpicky about it. Leave it to the author's judgement if such a documentation is necessary.

Files:

  • ext/node/ops/tls.rs
🧬 Code graph analysis (2)
tests/unit_node/tls_test.ts (1)
tests/unit/test_util.ts (1)
  • assertThrows (19-19)
ext/node/ops/tls.rs (1)
ext/node/lib.rs (1)
  • state (117-118)
⏰ 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). (9)
  • GitHub Check: test debug linux-aarch64
  • GitHub Check: test debug linux-x86_64
  • GitHub Check: test debug windows-x86_64
  • GitHub Check: test release linux-x86_64
  • GitHub Check: test debug macos-aarch64
  • GitHub Check: build libs
  • GitHub Check: lint debug linux-x86_64
  • GitHub Check: lint debug windows-x86_64
  • GitHub Check: lint debug macos-x86_64
🔇 Additional comments (1)
ext/node/ops/tls.rs (1)

49-52: Per‑isolate CA state and get/set ops look correct

Storing NodeTlsState in OpState and using try_borrow/try_borrow_mut to gate access is consistent with other extensions and keeps custom CA configuration isolate‑local. Using Option<Vec<String>> to distinguish the unset case from an explicitly empty CA list is also sensible, and the early return in op_get_root_certificates cleanly prefers custom certs over the webpki_root_certs fallback.

Given that the JS layer caches root certificates, cloning the stored vector when returning from op_get_root_certificates is a reasonable trade‑off and shouldn’t be a hot‑path cost.

Also applies to: 56-83, 85-97


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements the tls.setDefaultCACertificates API in Deno's Node.js compatibility layer, enabling applications to programmatically override the global CA certificate store for custom root CAs or testing environments.

Key Changes:

  • Added native state management (NodeTlsState) in Rust to store custom certificates per-isolate with proper worker isolation
  • Implemented setDefaultCACertificates function in the TypeScript polyfill with input validation and cache invalidation
  • Added comprehensive unit tests for the new API including validation checks

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
tests/unit_node/tls_test.ts Added unit tests for setDefaultCACertificates API including existence check, input validation, and basic acceptance test; minor formatting changes to existing tests
ext/node/polyfills/tls.ts Implemented setDefaultCACertificates function with input validation, integrated with native ops, and added cache invalidation for lazyRootCertificates; minor formatting changes to class declarations
ext/node/ops/tls.rs Added NodeTlsState struct for storing custom CA certificates and implemented op_set_default_ca_certificates op; updated op_get_root_certificates to check for custom certificates before returning defaults
ext/node/lib.rs Registered the new op_set_default_ca_certificates op in the deno_node extension

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/unit_node/tls_test.ts (1)

467-475: Consider adding a functional verification test.

This test only verifies that setDefaultCACertificates doesn't throw when given a valid input. It doesn't verify that rootCertificates actually reflects the updated certificates after calling the function. A more complete test would check that the change propagates correctly:

Deno.test("tls.setDefaultCACertificates updates rootCertificates", () => {
  const testCert = `-----BEGIN CERTIFICATE-----
MIIBkTCB+wIJAKHHCgVZU1FFMA0GCSqGSIb3DQEBCwUAMBExDzANBgNVBAMMBnRl
c3RDQTAeFw0yMDAxMDEwMDAwMDBaFw0zMDAxMDEwMDAwMDBaMBExDzANBgNVBAMM
BnRlc3RDQTCB
-----END CERTIFICATE-----`;

  tls.setDefaultCACertificates([testCert]);
  assertEquals(tls.rootCertificates.length, 1);
  assertEquals(tls.rootCertificates[0], testCert);
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c27d8f0 and 2d2b31d.

📒 Files selected for processing (4)
  • ext/node/lib.rs (1 hunks)
  • ext/node/ops/tls.rs (2 hunks)
  • ext/node/polyfills/tls.ts (4 hunks)
  • tests/unit_node/tls_test.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
ext/**/lib.rs

📄 CodeRabbit inference engine (CLAUDE.md)

Extensions should provide ops (operations) exposed to JavaScript in Rust code within ext/<extension_name>/ directories

Files:

  • ext/node/lib.rs
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: For debugging Rust code, set breakpoints in IDE debuggers (VS Code with rust-analyzer, IntelliJ IDEA) or use lldb directly
Use eprintln!() or dbg!() macros for debug prints in Rust code

Files:

  • ext/node/lib.rs
  • ext/node/ops/tls.rs

⚙️ CodeRabbit configuration file

Don't worry about coverage of Rust docstrings. Don't be nitpicky about it. Leave it to the author's judgement if such a documentation is necessary.

Files:

  • ext/node/lib.rs
  • ext/node/ops/tls.rs
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js}: For JavaScript runtime debugging, enable V8 inspector with --inspect-brk flag and connect Chrome DevTools to chrome://inspect
Use console.log() for debug prints in JavaScript runtime code

Files:

  • ext/node/polyfills/tls.ts
  • tests/unit_node/tls_test.ts
🧬 Code graph analysis (2)
ext/node/lib.rs (1)
ext/node/ops/tls.rs (1)
  • op_set_default_ca_certificates (86-97)
ext/node/polyfills/tls.ts (1)
ext/node/ops/tls.rs (1)
  • op_set_default_ca_certificates (86-97)
⏰ 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). (9)
  • GitHub Check: Agent
  • GitHub Check: CodeQL analysis (javascript-typescript)
  • GitHub Check: test debug linux-aarch64
  • GitHub Check: test debug linux-x86_64
  • GitHub Check: test release linux-x86_64
  • GitHub Check: test debug macos-aarch64
  • GitHub Check: test debug windows-x86_64
  • GitHub Check: test debug macos-x86_64
  • GitHub Check: build libs
🔇 Additional comments (10)
ext/node/lib.rs (1)

389-394: LGTM!

The new op_set_default_ca_certificates operation is correctly registered alongside the existing TLS operations.

tests/unit_node/tls_test.ts (2)

151-151: LGTM!

Minor formatting change, no functional impact.


372-375: LGTM!

Template literals now correctly form the file paths for reading TLS test data.

ext/node/ops/tls.rs (3)

49-52: LGTM!

The NodeTlsState struct is straightforward and appropriate for per-isolate TLS state management.


56-83: LGTM!

The updated op_get_root_certificates correctly prioritizes custom CA certificates when set, falling back to the default webpki root certificates otherwise.


85-97: Node.js intentionally lacks direct reset mechanism; this is expected parity, not a limitation.

Node.js tls.setDefaultCACertificates() similarly provides no direct way to reset to system defaults. The documented workaround is tls.setDefaultCACertificates(tls.getCACertificates('system')). Since the current implementation mirrors Node.js behavior, consider whether Deno should support the same pattern or if this API surface is intentionally simplified. If supporting system CA retrieval is desired, that would require additional infrastructure (a getCACertificates equivalent), not changes to op_set_default_ca_certificates itself.

ext/node/polyfills/tls.ts (4)

10-13: LGTM!

Import added correctly for the new operation.


104-122: LGTM on validation logic.

Input validation is solid - checks for array type and string elements before calling the native op. Cache invalidation by setting lazyRootCertificates = null correctly triggers a refresh on next access.


140-140: LGTM!

Function correctly added to the default export.


41-48: This review comment appears to be based on a misunderstanding of the Node.js TLS API.

The concern assumes that setDefaultCACertificates resets lazyRootCertificates = null, triggering a re-populate of a frozen array on subsequent access. However, in Node.js (and Deno's polyfill), tls.rootCertificates is the immutable, bundled Mozilla CA store, and setDefaultCACertificates manages a separate internal default CA list—it does not reset or modify tls.rootCertificates itself.

If lazyRootCertificates is only populated once from op_get_root_certificates() and never reset to null, the freeze + re-populate issue described will not occur. The frozen array remains frozen, but it does not need to be repopulated because tls.rootCertificates is meant to be immutable and constant throughout the runtime's lifetime.

Likely an incorrect or invalid review comment.

@TarikSogukpinar
Copy link
Copy Markdown
Contributor Author

@bartlomieju could I get some review for this PR thank you

Copy link
Copy Markdown
Member

@littledivy littledivy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

@bartlomieju bartlomieju changed the title feat(node): add tls.setDefaultCACertificates support fix(ext/node): add tls.setDefaultCACertificates support Jan 7, 2026
@bartlomieju bartlomieju enabled auto-merge (squash) January 9, 2026 14:38
@bartlomieju bartlomieju merged commit be13de0 into denoland:main Jan 9, 2026
19 checks passed
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.

4 participants