nodejs / nodejs/node

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

Aperta
#63,313 4 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Lingua principale
JavaScript
Stelle
122k
Fork
37.3k
Merge medio
4g 2h
PR unite (30g)
283

Descrizione

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:

  • anthropics/claude-code#53660 — comprehensive root-cause analysis. Their measurement on a corporate Mac: 9.7s for 18 certs (530ms/cert), vs 0.3ms/cert for direct C calls using SecPolicyCreateBasicX509. They shipped a CLAUDE_CODE_CERT_STORE=bundled env var workaround.
  • github/copilot-cli#3330 (filing concurrently) — same call costs 5+ seconds on every copilot invocation. The CLI's code explicitly asks for "system" certs in addition to "bundled".
  • github/copilot-cli#1250 — Windows variant of the same call (different failure mode, same offending line).
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
  • #58990 — tracking issue for custom CA support; lists "Load system CA certificates off thread" as done, but off-thread loading doesn't fix the cost, only moves it. The root cause is the revocation policy.
  • #57163 — --use-system-ca intermediate-cert issues on Windows.
  • #58346 — request for env-var alternative to --use-system-ca.

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia in src/crypto/crypto_x509.cc, in node::crypto::ReadMacOSKeychainCertificates, e segui la relativa valutazione dei criteri di attendibilità. Esegui su macOS il riproduttore di misurazione dei tempi fornito tls.getCACertificates("system"), quindi verifica che la raccolta delle CA di sistema eviti il ritardo di trustd correlato alla revoca, preservando al contempo i certificati restituiti e il comportamento HTTPS.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
javascript, macos, node.js
Ambito
networking, operating-systems, security
Tipo di issue
Bug
Difficoltà
4/5
Tempo stimato
3-5 giorni
Stato di attività
Attiva
Chiarezza
Specificata chiaramente
Idoneità per principianti
58/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.