feat(sdks): honor custom TLS trust with a ca_bundle / caBundle option - #1657
feat(sdks): honor custom TLS trust with a ca_bundle / caBundle option#1657mishushakov wants to merge 6 commits into
ca_bundle / caBundle option#1657Conversation
…tion Every transport the SDKs build trusted only the default CA store, so a self-hosted deployment behind a private CA had no way to make the SDK validate against it: in Python `verify_ssl` on the generated clients was silently dropped once a pyqwest transport was installed, and JS never exposed a knob at all. Add a `ca_bundle` / `caBundle` connection option (explicit option → `E2B_CA_BUNDLE` → unset) and thread it through every transport in both SDKs — REST API, envd RPC and HTTP, volume content (including the streaming transports), and the template build-context upload — so a build can't validate against a CA the API calls around it ignore. The certificates are added to the default trust rather than replacing it. Python gains a `TransportConfig` that carries the proxy and the TLS trust, keys the transport caches, and builds the pyqwest keyword arguments in one place; the volume clients now reuse the shared `retrying_http_transport` instead of repeating it. `verify_ssl` raises and points at `ca_bundle` instead of being ignored. In JS the option is Node-only, since no other runtime lets the SDK configure TLS trust per connection, and setting it elsewhere raises. The build-context upload now goes through the SDK's dispatcher, so it honors `proxy` as well. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PR SummaryMedium Risk Overview Reviewed by Cursor Bugbot for commit 9662374. Bugbot is set up for automated code reviews on this repo. Configure here. |
🦋 Changeset detectedLatest commit: 9662374 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Package ArtifactsBuilt from 47a65a5. Download artifacts from this workflow run. JS SDK ( npm install ./e2b-2.38.4-python-sdk-honor-custom-tls-trust-verify-ssl-ca-bundle-on-sdk-320.0.tgzCLI ( npm install ./e2b-cli-2.16.2-python-sdk-honor-custom-tls-trust-verify-ssl-ca-bundle-on-sdk-320.0.tgzPython SDK ( pip install ./e2b-2.38.0+python.sdk.honor.custom.tls.trust.verify.ssl.ca.bundle.on.sdk.320-py3-none-any.whl |
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The upload had no dispatcher before, so it used undici's default agent (HTTP/1.1). Now that it goes through one for the proxy and CA bundle, opt out of h2 so the framing this path is picky about is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fcafd1fdf0
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| ) | ||
| } | ||
|
|
||
| return [...tls.rootCertificates, pem] |
There was a problem hiding this comment.
Preserve Node's full default CA set
When caBundle is set in a Node process that already extends TLS trust via NODE_EXTRA_CA_CERTS, --use-system-ca, or tls.setDefaultCACertificates, this replaces the effective default trust with only tls.rootCertificates plus the new bundle. Because Node treats the ca option as a replacement and tls.rootCertificates is only the bundled CA subset, SDK requests through this dispatcher can start failing certificate verification for hosts that previously relied on the configured default CA store. Please build from Node's effective defaults (for example tls.getCACertificates?.('default') with an older-Node fallback) before appending the bundle.
Useful? React with 👍 / 👎.
…ndle asks otherwise The Bun/Deno/workerd legs run the same suites as Node, where two things broke: the fetcher-cache tests configured a CA bundle in runtimes that reject it, and routing the build-context upload through `buildDispatchedFetch` hit the shared setup's undici-unavailable mock, which dropped it onto a global fetch that chunks the body on Deno — the framing #1243 guards against. The upload now builds a dispatcher only when a CA bundle needs one (and refuses one outside Node), keeping every other build on the path it has always used, and its suite opts out of the mock so it exercises the real undici either way. The CA-bundle cache assertions moved to Node-only tests. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d dispatcher
`tls.rootCertificates` is only Node's bundled set, so a process that had
already extended trust through NODE_EXTRA_CA_CERTS, --use-system-ca or
setDefaultCACertificates lost those additions the moment a `caBundle`
replaced `ca`. Build on `tls.getCACertificates('default')` where it
exists (Node >= 22.15), which reports what Node would actually trust.
The upload dispatcher is also cached per CA bundle now: a build
uploading several files was re-reading the bundle and leaving an Agent
behind per upload.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Addressed the automated review:
Also narrowed the upload change: it only builds its own dispatcher when a CA bundle needs one, so every other build stays on undici's default agent — the path that carries the Content-Length framing presigned uploads require (#1243). Dropping it onto a dispatcher is what broke the Deno leg. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9662374. Configure here.
| inflightLimit: 0, | ||
| allowH2: false, | ||
| ...transport, | ||
| }) |
There was a problem hiding this comment.
Cached upload agent serializes parallels
Medium Severity
uploadFetch now reuses one dispatcher with connections: 1 for every build-context upload that shares a CA bundle. Template builds kick off those uploads in parallel, so with caBundle set they queue on a single HTTP/1.1 connection instead of running concurrently as they did when each upload built its own agent.
Reviewed by Cursor Bugbot for commit 9662374. Configure here.


Closes SDK-320.
Every transport the SDKs build trusted only the default CA store, so a self-hosted deployment behind a private CA had no way to make the SDK validate against it. In Python the gap was silent:
AuthenticatedClient._verify_sslonly ever configures httpx's default transport, and since the pyqwest migration every client is handed an explicit transport — soverify=self._verify_sslhas been dead for every REST call. JS never exposed a knob at all.What this adds
A
ca_bundle/caBundleconnection option — the path of a PEM file — resolving through the standard precedence (explicit option →E2B_CA_BUNDLE→ unset), wired into every transport in both SDKs:The certificates are trusted in addition to the default store (
tls_include_system_certsstays on in Python, the bundle is appended totls.rootCertificatesin JS), so a deployment that mixes a private CA with public ones keeps working.Usage
Volumes and template builds take the same option:
Nothing fails silently
verify_sslon the SDK's API/volume client factories now raisesInvalidArgumentExceptionpointing atca_bundleinstead of being ignored.ssl.SSLContextand disabling verification have no pyqwest equivalent and are named as unsupported in the message.undicican't be loaded, raises instead of quietly connecting with the trust it was meant to extend.Refactors that came with it
TransportConfigNamedTuple now carries the proxy and the TLS trust, keys every transport cache, and produces the pyqwest keyword arguments in one place — the six transport construction sites no longer each spell outtls_include_system_certs=True. The volume clients reuse the sharedretrying_http_transportinstead of repeating its body.FetchTransportOptsobject ({ proxy, caBundle }) instead of a bare proxy string, and cache per combination.Tests
tests/test_ca_bundle.py, newtrustmedev dependency): an HTTPS server whose certificate only a test CA vouches for, so the assertions are real handshakes — the API, envd, volume, and template-upload paths (sync and async) connect withca_bundleand fail without it. Plus precedence, propagation throughget_api_params, cache keying,verify_sslrejection, and the bundle-loading errors.tests/caBundle.test.ts): the certificates reach theAgent'sconnectand theProxyAgent'srequestTls/proxyTlson top of the default roots, the error paths, and the non-Node rejection; plusE2B_CA_BUNDLEprecedence and per-bundle fetcher caching.🤖 Generated with Claude Code