Commit 9494c55
fix(tls): support custom CA certificates for corporate proxies (CLI-1K6)
## Summary
- Add custom CA certificate support so the CLI works behind corporate
TLS-intercepting proxies (CLI-1K6)
- Bun's `fetch()` doesn't honor `NODE_EXTRA_CA_CERTS` natively — we now
read it and pass CA certs via Bun's `fetch({ tls: { ca } })` option
- On Node 24+, call `tls.setDefaultCACertificates()` to inject CAs into
the process-wide trust store
- Add `sentry cli defaults ca-cert` for persistent CA registration that
also silences the SaaS security warning
## Details
### Root cause
Users behind corporate TLS proxies (Zscaler, Netskope, Palo Alto) get
`Error: unable to get local issuer certificate` because Bun uses
BoringSSL with Mozilla's compiled-in CA bundle and doesn't read
`NODE_EXTRA_CA_CERTS` or the OS certificate store.
### Fix
New `src/lib/custom-ca.ts` module reads CA certs from (in priority
order):
1. `sentry cli defaults ca-cert` (stored in SQLite)
2. `NODE_EXTRA_CA_CERTS` env var
Custom CAs are concatenated with `tls.rootCertificates` (Mozilla's
built-in bundle) to preserve additive semantics — only adding CAs, never
replacing the default bundle.
**Runtime behavior:**
| Runtime | Mechanism |
|---------|-----------|
| Bun (compiled binary) | `fetch({ tls: { ca: combined } })` —
Bun-specific option |
| Node 24+ (npm) | `tls.setDefaultCACertificates([...rootCerts,
custom])` — process-wide |
| Node 22 (npm) | `NODE_EXTRA_CA_CERTS` handled natively by Node;
`defaults ca-cert` requires Node 24+ |
PEM validation logic is shared via `readCaCertFile()` — used by both the
eager validation in `sentry cli defaults ca-cert` and the lazy loading
at fetch time.
### Security model
When custom CAs come from env vars (not stored defaults) AND the target
is `*.sentry.io`, a one-time `log.warn()` fires. This creates a forensic
trail in CI logs — an attacker who can set env vars in a CI step could
inject a rogue CA + proxy to intercept tokens. `sentry cli defaults
ca-cert` silences the warning (user has acknowledged). See plan file for
the full threat model discussion.
### Error handling improvements
- TLS cert errors are detected by pattern (walking `error.cause` chain
with cycle detection for Node.js's `TypeError: fetch failed` wrapper)
and wrapped in `ApiError`
- `buildTlsErrorDetail()` is shared between both fetch paths and
branches on whether custom CAs are already loaded
- TLS errors are not retried (deterministic)
- Only CA trust errors are matched (not `CERT_HAS_EXPIRED` or hostname
mismatches which need different guidance)
### Changes
| File | Change |
|------|--------|
| `src/lib/custom-ca.ts` | **New** — CA loading, `readCaCertFile`,
`buildTlsErrorDetail`, `isTlsCertError`, Node 24+ injection, SaaS
warning |
| `src/lib/sentry-client.ts` | Wire custom TLS into `fetchWithTimeout`,
TLS error handling |
| `src/lib/oauth.ts` | Wire custom TLS into OAuth fetch, TLS error
handling |
| `src/lib/db/defaults.ts` | Add `defaults.ca-cert` key |
| `src/commands/cli/defaults.ts` | Add `ca-cert` handler with aliases,
eager PEM validation |
| `src/lib/formatters/human.ts` | Add `"ca-cert": "CA Certificate"` to
defaults display |
| `src/lib/env-registry.ts` | Register `NODE_EXTRA_CA_CERTS` |
| `script/generate-docs-sections.ts` | Add env var to self-hosted docs
table |
| `test/lib/custom-ca.test.ts` | **New** — 24 tests |
Fixes CLI-1K6
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>1 parent 6ef87e7 commit 9494c55
12 files changed
Lines changed: 769 additions & 63 deletions
File tree
- docs/src/content/docs
- script
- src
- commands/cli
- lib
- db
- formatters
- test
- commands/cli
- lib
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
| 80 | + | |
80 | 81 | | |
81 | 82 | | |
82 | 83 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
362 | 362 | | |
363 | 363 | | |
364 | 364 | | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
365 | 369 | | |
366 | 370 | | |
367 | 371 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
11 | 12 | | |
12 | 13 | | |
| 14 | + | |
13 | 15 | | |
14 | 16 | | |
15 | 17 | | |
| 18 | + | |
16 | 19 | | |
17 | 20 | | |
18 | 21 | | |
19 | 22 | | |
20 | 23 | | |
| 24 | + | |
21 | 25 | | |
22 | 26 | | |
23 | 27 | | |
24 | 28 | | |
25 | 29 | | |
| 30 | + | |
26 | 31 | | |
27 | 32 | | |
28 | 33 | | |
| |||
47 | 52 | | |
48 | 53 | | |
49 | 54 | | |
50 | | - | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
51 | 62 | | |
52 | 63 | | |
53 | 64 | | |
| |||
131 | 142 | | |
132 | 143 | | |
133 | 144 | | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
134 | 164 | | |
135 | 165 | | |
136 | 166 | | |
| |||
140 | 170 | | |
141 | 171 | | |
142 | 172 | | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
143 | 176 | | |
144 | 177 | | |
145 | 178 | | |
| |||
196 | 229 | | |
197 | 230 | | |
198 | 231 | | |
| 232 | + | |
199 | 233 | | |
200 | 234 | | |
201 | 235 | | |
| |||
206 | 240 | | |
207 | 241 | | |
208 | 242 | | |
209 | | - | |
| 243 | + | |
| 244 | + | |
210 | 245 | | |
211 | 246 | | |
212 | 247 | | |
| |||
260 | 295 | | |
261 | 296 | | |
262 | 297 | | |
263 | | - | |
| 298 | + | |
264 | 299 | | |
265 | 300 | | |
266 | 301 | | |
| |||
0 commit comments