Inject the corporate CA certificate into containers
Build App / compute-version (pull_request) Successful in 6s
Build App / build-macos (pull_request) Successful in 2m30s
Build App / build-windows (pull_request) Successful in 5m16s
Build Container / build-container (pull_request) Successful in 10m15s
Build App / build-linux (pull_request) Successful in 6m35s
Build App / create-tag (pull_request) Skipped
Build App / sync-to-github (pull_request) Skipped

Behind a TLS-terminating corporate proxy every HTTPS call inside a container
fails — npm, pip, git, curl, the browser-view pane, and Claude Code's own API
requests. There was no mechanism at all: installing the certificate by hand
inside a container is lost on Reset and had to be repeated per project.

A global CA path in AppSettings with a per-project override on Project, taking
either a single certificate file or a directory. It is bind-mounted read-only
at /tmp/.host-ca (mirroring /tmp/.host-ssh and /tmp/.host-aws) and applied by
entrypoint.sh on every start, so it survives recreation, migration and Reset.

Four things this gets right that are easy to get wrong:

* update-ca-certificates globs *.crt case-sensitively, so a .pem that is merely
  copied in is ignored in silence. Certificates are renamed, by
  container_cert_name() in Rust and a mirrored few lines of shell.
* The system store only serves curl/git/apt. Node — and so Claude Code itself —
  needs NODE_EXTRA_CA_CERTS, Python needs REQUESTS_CA_BUNDLE/SSL_CERT_FILE, and
  Chromium reads neither: it wants ~/.pki/nssdb, seeded with certutil
  (libnss3-tools, added to the image).
* Those vars are set from Rust at creation, never exported by the entrypoint —
  a terminal is a docker exec and sees nothing the entrypoint exported. They are
  emitted empty when no CA is configured, since docker commit bakes env into the
  snapshot image.
* triple-c.ca-fingerprint hashes the certificate bytes as well as the path, so
  a CA rotated in at the same location still forces a recreation.

Verified end to end against a real container and a self-signed CA: curl, node,
python and git all complete a TLS handshake against a server signed by it and
all three fail in the same container without it; the env vars are visible from
a docker exec session; the store is cleaned when the setting is cleared.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KSP2KNPhuWKQ4DL5TZEn3k
This commit is contained in:
Claude
2026-08-10 10:40:21 -07:00
parent 584fcdd837
commit 7a5823cb2b
19 changed files with 1367 additions and 5 deletions
+32 -2
View File
@@ -824,8 +824,8 @@ Notes:
## Settings
Access global settings via the **Settings** tab in the sidebar. The panel is a set of collapsible
sections: **General**, **Claude Authentication**, **Backends**, **Container**, **Git / SSH**,
**Tools** and **Updates**.
sections: **General**, **Claude Authentication**, **Backends**, **Container**, **Certificates**,
**Git / SSH**, **Tools** and **Updates**.
### Claude Authentication
@@ -855,6 +855,36 @@ Environment variables applied to **all** project containers. Per-project variabl
Path to your SSH key directory (typically `~/.ssh`). This is mounted into **all** containers that don't have a per-project SSH path set. Per-project SSH paths take precedence.
### Corporate CA Certificate
If your organisation's network inspects TLS (a corporate proxy, a VPN that terminates HTTPS at the
edge), containers need your organisation's root certificate or **every** HTTPS call inside them
fails — `npm install`, `pip`, `git clone` over HTTPS, `curl`, the browser-view pane, and Claude
Code's own calls to the API.
Point this at either a **single certificate file** or a **folder** of them. It is mounted read-only
into every container and applied on every start, so it survives container recreation, base-image
migration and Reset — unlike a certificate you install by hand inside a running container, which is
lost the first time any of those happens.
The status line under the field tells you how many certificates were found and the names they will
be installed as inside the container. That rename matters: the container's trust store only reads
files ending in `.crt`, so a `.pem` is renamed rather than merely copied, which is the step that is
easiest to get wrong by hand.
Inside the container the certificate is trusted by:
| Consumer | How |
|---|---|
| curl, git, apt, wget | the system trust store (`update-ca-certificates`) |
| Node, npm, **Claude Code itself** | `NODE_EXTRA_CA_CERTS` |
| Python, pip, requests | `REQUESTS_CA_BUNDLE` and `SSL_CERT_FILE` |
| Chrome / Chromium (browser view) | its own NSS database at `~/.pki/nssdb` |
A per-project override lives in **Project Home → Config → Access**; leave it blank to use this
global setting. Changing either recreates the project's container on its next start — replacing the
certificate file in place counts as a change, so a rotated CA is picked up too.
### Default Git Name / Email
Sets `git user.name` and `git user.email` inside all containers. Per-project Git Name / Email settings take precedence. This is useful so you don't have to set the same name and email on every project.