Skip to content

macOS: tls.getCACertificates('system') uses revocation-enabled trust policy, causing 5-10s startup on machines with network filters #63313

@Joi

Description

@Joi

Version

v24.15.0 (also v25 per anthropics/claude-code#53660 measurements)

Platform

Darwin (macOS 26.4.1, Apple Silicon Mac16,12). Reproduces on macOS Sonoma/Sequoia per the linked downstream reports.

Subsystem

tls, crypto

What steps will reproduce the bug?

On a Mac with any active network-flow filter (corporate NetworkExtension, content filter, or even a userland VPN-style daemon like ZeroTier), and a moderately-sized login keychain:

$ node -e 'const t0=process.hrtime.bigint();
           const c = require("tls").getCACertificates("system");
           console.log(`returned=${c.length} elapsed=${(Number(process.hrtime.bigint()-t0)/1e6).toFixed(0)}ms`)'
returned=34 elapsed=5287ms

The same call against "bundled" returns in ~0.1ms. HTTPS request times via --use-system-ca are ~100× slower than --use-bundled-ca (5093ms vs 103ms to api.github.com).

The bug is in node::crypto::ReadMacOSKeychainCertificates (src/crypto/crypto_x509.cc / wherever the macOS branch lives). It calls SecTrustEvaluateWithError with a revocation-enabled policy when collecting trust anchors, which causes trustd to attempt AIA/OCSP/CRL fetches for every cert. When a NetworkExtension content filter is present, each flow goes through per-flow cryptographic signing (~1-2ms × hundreds of certs = 5-10 seconds total) before being denied. Without a flow filter the cost is smaller but still present.

How often does it reproduce?

Every invocation. Stable across reboots and Node versions in the v24.x / v25 line.

What is the expected behavior? Why is that the expected behavior?

tls.getCACertificates("system") should be fast (single-digit milliseconds for a typical keychain) because it's collecting trust anchors, not validating a server chain. Revocation checks are pointless during anchor collection — a trust anchor's revocation status is determined by user/admin trust settings, not by an OCSP responder.

What do you see instead?

5-10+ seconds of latency, predominantly in synchronous XPC round-trips to trustd. Effectively blocks Node CLI startup any time the CLI's CA loader touches the system store. Several Node-based CLIs are affected in practice:

Additional information

Proposed fix

Per the anthropics/claude-code#53660 analysis, the call should use SecPolicyCreateBasicX509() (no revocation) when enumerating system CAs to use as trust anchors. Alternatively, the function could skip trust evaluation entirely and call SecTrustSettingsCopyCertificates(kSecTrustSettingsDomainAdmin/System) to collect anchors without validation. Either approach removes revocation from the hot startup path.

Reproducer kit

# Standalone measurement
node -e 'const t0=process.hrtime.bigint();
         require("tls").getCACertificates("system");
         console.log(`${(Number(process.hrtime.bigint()-t0)/1e6).toFixed(0)}ms`)'

# HTTPS A/B
node --use-bundled-ca -e 'require("https").get("https://api.github.com/zen",r=>r.on("data",()=>{}).on("end",()=>{}))'   # ~100ms
node --use-system-ca  -e 'require("https").get("https://api.github.com/zen",r=>r.on("data",()=>{}).on("end",()=>{}))'   # ~5s

# Sample to confirm the syscall pattern
node --use-system-ca -e 'require("tls").getCACertificates("system")' &
PID=$!; sleep 0.1; sample $PID 3 -mayDie

Sample output during the wait deterministically shows:

node::crypto::ReadMacOSKeychainCertificates
  → node::crypto::IsCertificateTrustedForPolicy
    → SecTrustEvaluateWithError
      → securityd_send_sync_and_do (XPC to trustd)

Why this matters beyond corporate networks

Anthropic's analysis (and the original v24 design assumption) treats this as a "corporate Macs only" problem. In practice, any userland flow-handling daemon — ZeroTier, Tailscale, Cloudflare WARP, certain VPN clients — places similar code in the path. The fix doesn't depend on detecting the filter; it's simply correct to skip revocation when enumerating trust anchors.

Related Node-side tracking

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions