github / github/copilot-cli

macOS: explicit `tls.getCACertificates("system")` call adds 5+ seconds to every CLI invocation

Open
#3,330 1 comment 0 reactions 0 assignees View on GitHub
area:networking
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.