Repository navigation
feat(express): add default domain-scoped CA trust for BitGo connections - #10073
Open
pranishnepal wants to merge 1 commit into
Open
pranishnepal wants to merge 1 commit into
pranishnepal wants to merge 1 commit into
Conversation
Contributor
Contributor
|
|
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.
Tip: disable this comment in your organization's Code Review settings.
pranishnepal
marked this pull request as draft
October 27, 2026 16:54
pranishnepal
force-pushed
the
pranishnepal/wcn-3731-add-domain-scoped-ca-trust-and-optional-spki-pinning-to
branch
2 times, most recently
from
October 28, 2026 05:03
7fc7465 to
c8d0fe9
Compare
pranishnepal
marked this pull request as ready for review
October 28, 2026 05:17
pranishnepal
force-pushed
the
pranishnepal/wcn-3731-add-domain-scoped-ca-trust-and-optional-spki-pinning-to
branch
from
October 28, 2026 17:38
c8d0fe9 to
d2625bb
Compare
Restrict outbound HTTPS connections to official BitGo domains
(*.bitgo.com, *.bitgo-test.com, *.bitgo-staging.com, *.bitgo-dev.com,
and their apex domains) to a bundled multi-provider root CA store
(Google Trust Services, Amazon Trust Services, DigiCert, Let's
Encrypt/ISRG, and Sectigo) while keeping the system trust store for all
non-BitGo destinations:
- sdk-api: add createBitGoTlsPolicy({ ca, hosts }) and enforce it in
BitGoAPI.getAgentRequest (refusing redirects and plain http on covered
hosts)
- express: enable the bundled 18-root CA policy by default at startup,
with a single opt-out flag (--disablepincerts / BITGO_DISABLE_PIN_CERTS)
and automatic proxy fallback when BITGO_USE_PROXY is set
- scripts/verify-bitgo-policy-data.sh: CI check verifying all bundled
roots are self-signed, valid (>30d), and cover all BitGo domains
(plus --live handshake verification)
TICKET: WCN-3731
pranishnepal
force-pushed
the
pranishnepal/wcn-3731-add-domain-scoped-ca-trust-and-optional-spki-pinning-to
branch
from
October 28, 2026 17:48
d2625bb to
21198bd
Compare
pranishnepal
commented
Oct 28, 2026
| "unprettied": "grep -R -L --include '*.ts' --include '*.js' --include '*.json' '@prettier' src test", | ||
| "fmt": "yarn prettier --write '{src,test}/**/*.{ts,js,json}'" | ||
| "fmt": "yarn prettier --write '{src,test}/**/*.{ts,js,json}'", | ||
| "test:data": "bash ../../scripts/verify-bitgo-policy-data.sh --live" |
Contributor
Author
There was a problem hiding this comment.
will run prior to the unit test and fail the CI if it needs updating ~30d prior to the actual expiration date
@bitgo/express: $ bash ../../scripts/verify-bitgo-policy-data.sh --live
@bitgo/express: ok: 18 self-signed CA certs valid (>30d), 13 BitGo hosts covered, http downgrades throw
@bitgo/express: live ok: https://app.bitgo.com/ (HTTP 302)
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Restricts outbound HTTPS connections from BitGo Express (and
@bitgo/sdk-apiwhentlsPolicyis configured) to official BitGo domains (*.bitgo.com,*.bitgo-test.com,*.bitgo-staging.com,*.bitgo-dev.com, and their apex domains) to a bundled multi-provider Root CA store (modules/express/bitgo-ca.pem) by default, while keeping the standard Node.js / system trust store for all non-BitGo destinations (external signer, block explorers, webhooks, KMS).modules/express/bitgo-ca.pem): Pre-includes the self-signed Root CAs for the 5 tier-1 providers BitGo uses today or would fail over to (Google Trust Services, Amazon Trust Services, DigiCert, Let's Encrypt / ISRG, and Sectigo). This shrinks the trusted CA surface for BitGo domains from ~150 global CAs down to 5 providers (coveringapp.*,admin.*, andcdn.bitgo.comout of the box) without breaking across routine leaf renewals, intermediate rotations, or CA migrations.BitGoAPI.getAgentRequest): Every BitGo-bound request (requestPatch,fetchConstants,executeAssetRequest) goes throughgetAgentRequest, which attaches the restrictedhttps.Agent({ ca }), refuses redirects (.redirects(0)) on covered hosts so a redirect cannot escape the domain policy, and refuses plaintexthttp://downgrades on covered hosts.disablePinCerts: false). Operators can opt out with--disablepincertsorBITGO_DISABLE_PIN_CERTS=true. IfBITGO_USE_PROXYis set, Express logs a startup warning and skips the bundled CA policy so existing proxy deployments continue to work.Why this shape
NODE_EXTRA_CA_CERTScan only add trust; nothing in Node.js lets you remove a compromised CA from the global trust store, and replacing the global store would break non-BitGo outbound calls. Passingcaon a dedicatedhttps.Agentreplaces the trust store only for connections routed through that agent. PairingappliesTo(url)withagentinsideBitGoTlsPolicyguarantees that covered BitGo domains use the restricted bundle while all other destinations keep the system store.What's in here
@bitgo/sdk-api(modules/sdk-api/src/tlsPolicy.ts,bitgoAPI.ts,types.ts):createBitGoTlsPolicy({ ca, hosts })splits and validates every PEM certificate incaviacrypto.X509Certificate, normalizes and validates host patterns viaparsePolicyHost, and returns{ agent, appliesTo(url) }.appliesTo(url)matches official BitGo hostnames (including trailing-dot FQDNs) and throws if a covered host is contacted over plainhttp://or a schemeless URL.getCaExpiries(ca)inspects certificate expiry dates for startup monitoring.BitGoAPI.getAgentRequest(bitgoAPI.ts:497-501) and wired intofetchConstants(bitgoAPI.ts:780).@bitgo/express(modules/express/src/tlsPolicy.ts,bitgo-ca.pem,config.ts,args.ts,expressApp.ts,clientRoutes.ts,fetchEncryptedPrivKeys.ts):modules/express/bitgo-ca.pem(18 self-signed Root CAs across GTS, Amazon, DigiCert, ISRG, and Sectigo).BITGO_DEFAULT_TLS_HOSTS(bitgo.com,.bitgo.com,bitgo-test.com,.bitgo-test.com,bitgo-staging.com,.bitgo-staging.com,bitgo-dev.com,.bitgo-dev.com).--disablepincerts/BITGO_DISABLE_PIN_CERTS(with proper boolean parsing soBITGO_DISABLE_PIN_CERTS=falsestays enabled).checkPreconditions(expressApp.ts:273) and warns viaconsole.warnif any bundled CA expires within 30 days.scripts/verify-bitgo-policy-data.sh,modules/express/package.json):expressunit-test(yarn test:data), verifying that all 18 certificates inbitgo-ca.pemparse, are self-signed CA roots (issuer === subjectandcert.ca === true), are>30days from expiry, and cover all 13 BitGo apex/subdomain hosts.bash scripts/verify-bitgo-policy-data.sh --liveto verify real TLS handshakes against liveapp.*,admin.*, andcdn.bitgo.comendpoints using onlybitgo-ca.pem.How it was tested
modules/sdk-api/test/unit/tlsPolicy.ts(18/18),modules/express/test/unit/tlsPolicy.ts,config.ts,bitgoExpress.ts(55/55), and fullexpressunit suite.scripts/verify-bitgo-policy-data.sh --live): Verified live TLS handshakes againstapp.bitgo.com,app.bitgo-test.com,app.bitgo-staging.com,app.bitgo-dev.com,admin.bitgo*.com, andcdn.bitgo.comusing onlybitgo-ca.pem.app.bitgo-staging.com): Verified authenticated SDK/Express calls (fetchConstants,me(), wallet read, session unlock) with the default 18-root policy enabled, plus negative rejection when an unapproved CA bundle is used (Linear test evidence).TICKET: WCN-3731