macOS: `tls.getCACertificates('system')` uses revocation-enabled trust policy, causing 5-10s startup on machines with network filters
Personne n'a encore pris cette issue.
- Langage dominant
- JavaScript
- Étoiles
- 122k
- Forks
- 37.3k
- Merge moyen
- 4 j 2 h
- PR mergées (30 j)
- 283
Description
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 aCLAUDE_CODE_CERT_STORE=bundledenv var workaround. - github/copilot-cli#3330 (filing concurrently) — same call costs 5+ seconds on every
copilotinvocation. 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-caintermediate-cert issues on Windows. - #58346 — request for env-var alternative to
--use-system-ca.
Guide de contribution
Ouvrir le guide de contribution
Par où commencer
- Lisez l'issue en entier, puis le guide de contribution du projet.
- Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
- Forkez le dépôt et travaillez sur une branche.
- Ouvrez une pull request qui référence le numéro de l'issue.
Piste de recherche
Commencez dans src/crypto/crypto_x509.cc, au niveau de node::crypto::ReadMacOSKeychainCertificates, et suivez son évaluation de la politique de confiance. Exécutez sur macOS le reproducteur de mesure du temps fourni tls.getCACertificates("system"), puis vérifiez que la collecte des CA système évite le délai de trustd lié à la révocation tout en préservant les certificats renvoyés et le comportement HTTPS.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- javascript, macos, node.js
- Domaine
- networking, operating-systems, security
- Type d'issue
- Bug
- Difficulté
- 4/5
- Temps estimé
- 3-5 jours
- Activité
- Active
- Clarté
- Clairement spécifiée
- Accessibilité débutants
- 58/100