Skip to content

fix(tls): support custom CA certificates for corporate proxies (CLI-1K6)#901

Merged
BYK merged 13 commits intomainfrom
fix/tls-custom-ca-support
May 1, 2026
Merged

fix(tls): support custom CA certificates for corporate proxies (CLI-1K6)#901
BYK merged 13 commits intomainfrom
fix/tls-custom-ca-support

Conversation

@BYK
Copy link
Copy Markdown
Member

@BYK BYK commented May 1, 2026

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://cli.sentry.dev/_preview/pr-901/

Built to branch gh-pages at 2026-05-01 18:45 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 1, 2026

Codecov Results 📊

6521 passed | Total: 6521 | Pass Rate: 100% | Execution Time: 0ms

📊 Comparison with Base Branch

Metric Change
Total Tests 📈 +24
Passed Tests 📈 +24
Failed Tests
Skipped Tests

All tests are passing successfully.

❌ Patch coverage is 72.22%. Project has 13305 uncovered lines.
✅ Project coverage is 76.05%. Comparing base (base) to head (head).

Files with missing lines (5)
File Patch % Lines
src/lib/custom-ca.ts 79.73% ⚠️ 30 Missing
src/commands/cli/defaults.ts 41.67% ⚠️ 14 Missing
src/lib/oauth.ts 52.17% ⚠️ 11 Missing
src/lib/sentry-client.ts 47.06% ⚠️ 9 Missing
src/lib/db/defaults.ts 92.31% ⚠️ 1 Missing
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
+ Coverage    75.99%    76.05%    +0.06%
==========================================
  Files          295       296        +1
  Lines        55386     55555      +169
  Branches         0         0         —
==========================================
+ Hits         42090     42250      +160
- Misses       13296     13305        +9
- Partials         0         0         —

Generated by Codecov Action

Comment thread src/lib/custom-ca.ts
Comment thread src/lib/custom-ca.ts Outdated
Comment thread src/lib/custom-ca.ts
Comment thread src/lib/custom-ca.ts Outdated
Comment thread src/lib/oauth.ts
@BYK BYK force-pushed the fix/tls-custom-ca-support branch from 731c2f0 to 54ee5cb Compare May 1, 2026 04:39
Comment thread src/lib/custom-ca.ts
@BYK BYK force-pushed the fix/tls-custom-ca-support branch from 54ee5cb to fe6d962 Compare May 1, 2026 04:51
Comment thread src/lib/custom-ca.ts
Comment thread src/lib/sentry-client.ts Outdated
Comment thread src/lib/custom-ca.ts Outdated
Comment thread src/lib/db/defaults.ts
Comment thread src/lib/oauth.ts
BYK added 5 commits May 1, 2026 05:49
Add custom CA certificate loading so the CLI works behind TLS-intercepting
corporate proxies (Zscaler, Netskope, etc.). Bun uses BoringSSL with a
compiled-in Mozilla CA bundle and does not honor NODE_EXTRA_CA_CERTS or
SSL_CERT_FILE natively — we now read these env vars and pass the PEM
contents to Bun's fetch({ tls: { ca } }) option.

CA loading priority: stored default > NODE_EXTRA_CA_CERTS > SSL_CERT_FILE.
Users can register a CA cert via 'sentry cli defaults ca-cert /path/to/ca.pem'
which also silences a security warning logged when env-sourced CAs target
sentry.io (the warning creates a forensic trail for CI environments where
an attacker could inject a rogue CA via env vars).

Also improves TLS error handling:
- Detect TLS cert errors by pattern and wrap in ApiError (not raw Error)
- Show actionable guidance pointing to 'sentry cli defaults ca-cert'
- Don't retry TLS errors (deterministic — retrying is pointless)
- Fix async race in resolve(): cache the promise instead of a boolean
  flag so concurrent callers await the same I/O (Cursor Bugbot)
- Fix isTlsCertError to walk error.cause chain for Node.js fetch which
  wraps TLS errors in TypeError('fetch failed') (Seer)
- Add tests for error.cause detection
- Remove CERT_HAS_EXPIRED and ERR_TLS_CERT_ALTNAME_INVALID from TLS
  patterns — not CA trust issues, would produce misleading guidance (Seer)
- Extract root TLS error via getTlsCertErrorMessage() so Node.js
  'fetch failed' wrappers don't hide the real error (Cursor Bugbot)
- Wire getTlsCertErrorMessage into both sentry-client.ts and oauth.ts
@BYK BYK force-pushed the fix/tls-custom-ca-support branch from c44337a to acc094d Compare May 1, 2026 05:49
Comment thread src/commands/cli/defaults.ts Outdated
Comment thread test/lib/custom-ca.test.ts
BYK added 3 commits May 1, 2026 06:16
…() sync

- Extract readCaCertFile() as single source of truth for PEM validation
- defaults ca-cert handler now calls readCaCertFile instead of duplicating
- resolve() is now sync (readFileSync), eliminating the async race entirely
- getCustomTlsOptions() is no longer async — simpler call sites
- Add cycle detection (Set guard) in getTlsCertErrorMessage cause walker
- Extract buildTlsErrorDetail to custom-ca.ts so both sentry-client.ts
  and oauth.ts share the hasCustomCa branching logic
- Store resolvedLabel during resolve() so warnIfSaasWithEnvCa shows the
  actual source, not a re-read of env that may disagree
- getCustomCaSource() now calls resolve() to prevent stale 'none' if
  called before getCustomTlsOptions()
Comment thread src/lib/custom-ca.ts
Comment thread src/lib/custom-ca.ts
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1e6603a. Configure here.

Comment thread src/lib/custom-ca.ts Outdated
BYK added 2 commits May 1, 2026 17:34
Bun's tls.ca replaces the default Mozilla CA bundle rather than
supplementing it. Concatenate via [...rootCertificates, customPem]
to match NODE_EXTRA_CA_CERTS additive behavior — without this,
setting a corporate CA would break all public CA trust.
…ified experience

- Drop SSL_CERT_FILE support (neither Bun nor Node honor it natively)
- On Node 24+, call tls.setDefaultCACertificates() to inject custom CAs
  into the process-wide TLS trust store so Node's fetch() also works
- On Node 22, NODE_EXTRA_CA_CERTS is handled natively by Node
- Feature-detect setDefaultCACertificates (undefined on Node 22)
@BYK BYK merged commit 9494c55 into main May 1, 2026
26 checks passed
@BYK BYK deleted the fix/tls-custom-ca-support branch May 1, 2026 18:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant