macOS: explicit `tls.getCACertificates("system")` call adds 5+ seconds to every CLI invocation
- Dominant language
- Shell
- Stars
- 11.2k
- Forks
- 1.9k
- Avg merge
- 14h 16m
- Merged PRs (30d)
- 6
Description
### Describe the bug
GitHub Copilot CLI's CA loader calls `tls.getCACertificates("system")` on every invocation in addition to `"bundled"` and `"extra"`. On macOS, that call iterates every keychain cert and invokes `SecTrustEvaluateWithError` synchronously via XPC to `trustd` — which (because of a Node bug, see References) uses a revocation-enabled trust policy and triggers OCSP/CRL fetches per cert. On machines with any network-flow filter (corporate NetworkExtension, ZeroTier, etc.), each fetch pays a per-flow cost and totals **5-10 seconds of latency on every CLI invocation** — even sub-commands like `--version` that don't make HTTPS requests inherit the cost because the call happens during SEA startup.
The `"system"` arg is also functionally redundant with `"bundled"` for `*.github.com` / `*.githubcopilot.com` endpoints. See related #869 (which reports a separate failure caused by the duplicate-certs concatenation from this same code).
### Affected version
GitHub Copilot CLI 1.0.48 (also reproduced on 1.0.39). macOS 26.4.1, Apple Silicon. Node 24.15.0 inside the SEA.
### Steps to reproduce the behavior
```bash
$ time copilot --allow-all-tools -p "reply with exactly: ack"
ack
real 0m12.43s
user 0m1.98s
sys 0m0.17s
```
Wall-clock vs user CPU mismatch confirms it's I/O wait, not CPU work. `sample(1)` on the grandchild SEA process during the wait:
```
node::crypto::GetSystemCACertificates
node::crypto::ReadMacOSKeychainCertificates
node::crypto::IsCertificateTrustedForPolicy
node::crypto::IsCertificateTrustValid
SecTrustEvaluateWithError
SecTrustEvaluateIfNecessary
securityd_send_sync_and_do ← synchronous XPC to trustd
xpc_connection_send_message_with_reply_sync
mach_msg2_trap
```
Isolated reproduction with standalone Node 24 on the same machine:
```bash
$ node -e 'const t0=process.hrtime.bigint();
require("tls").getCACertificates("system");
console.log(`${(Number(process.hrtime.bigint()-t0)/1e6).toFixed(0)}ms`)'
5287ms
$ node -e 'const t0=process.hrtime.bigint();
require("tls").getCACertificates("bundled");
console.log(`${(Number(process.hrtime.bigint()-t0)/1e6).toFixed(0)}ms`)'
0.1ms
```
HTTPS request to `api.github.com`:
- default (bundled): **53ms**
- `node --use-bundled-ca …`: **103ms**
- `node --use-system-ca …`: **5093ms** — 96× slower
### Expected behavior
CLI startup should not pay multi-second latency for a CA loader that produces certs equivalent to the bundled Mozilla store.
### Root cause in copilot-cli source
Decoded from `app.js` (npm-loader fallback; the SEA's bundled JS contains the same logic):
```js
function buildCAList() {
const envCerts = ["NODE_EXTRA_CA_CERTS","SSL_CERT_FILE","CURL_CA_BUNDLE"].flatMap(readEnvCAFile);
return typeof tls.getCACertificates === "function"
? [...envCerts,
...tls.getCACertificates(), // default (bundled in Node 24+)
...tls.getCACertificates("system"), // ← THIS LINE
...tls.getCACertificates("bundled"), // bundled (already implied above)
...tls.getCACertificates("extra")]
: [...envCerts, ...tls.rootCertificates];
}
```
### Proposed fix
Remove the `tls.getCACertificates("system")` line. One-line patch. If retaining the option for users with private CAs is desired, gate it behind a `COPILOT_USE_SYSTEM_CA=1` env var defaulting OFF. The current always-on cost is borne by every macOS user even when they don't need it. This also resolves the duplicate-certs issue tracked at #869.
### Why this user sees the upper end
My login keychain had 1589 certs (mostly auto-imported S/MIME contact certs from Mail.app over 20 years). After pruning 1478 long-expired certs, the call dropped from ~10s to ~5s — the floor on this machine, set by ~270 still-valid certs each costing ~20 ms in `SecTrustEvaluateWithError`. ZeroTier's `feth1960` virtual interface and `zerotier-one` daemon further amplify per-flow cost for the trustd OCSP traffic.
Users without a flow filter and a smaller keychain see a smaller version of this same tax (1-2s baseline, per anthropics/claude-code#53660's measurements).
### Related
- **anthropics/claude-code#53660** — same Node-side root cause analyzed in detail (revocation-enabled trust policy in `SecTrustEvaluateWithError`). Their workaround was to ship a `CLAUDE_CODE_CERT_STORE=bundled` env var users can set.
- **#1250** — Windows variant of the same call: throws "X509 to PEM conversion" instead of slow execution. Different symptom, same offending line.
- **#869** — duplicate certs from this same `bundled + system` concatenation cause `Failed to list available models` for some users.
- **nodejs/node#58990** — Node tracking issue for custom CA support; needs a sub-issue for the revocation-policy perf bug specifically, which I'm filing separately.
Contributor guide
Research direction
Start in the decoded app.js buildCAList function and inspect the tls.getCACertificates calls, especially the system-store call. Remove the always-on system lookup, then verify that CLI startup no longer incurs the multi-second macOS delay and that the bundled CA behavior remains available.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- macos, node.js
- Domain
- cli, operating-systems, performance
- Issue type
- Bug
- Difficulty
- 1/5
- Estimated time
- Under an hour
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100