fix(pi): forward SSL_CERT_FILE into the sandbox so provider TLS works in Docker #87

Merged
weiwen merged 1 commit from fm/evie-sandbox-cert-fix into main 2026-08-05 02:46:44 +08:00
Owner

Root cause

Sandboxed pi sessions could not make TLS calls to the model provider, so every Telegram turn crashed with pi provider error: Connection error. after a ~20s hang.

evie's bwrap sandbox does --clearenv and then re-adds only an explicit base env allowlist (PATH, EVIE_CHAT_ID, EVIE_SOCKET), which never included a CA-certificate path. Under the old NixOS host, pi's TLS client happened to find a bundle at the default filesystem path the host provided. The nix-built Docker image has no /etc/ssl/certs/ca-certificates.crt (only ca-bundle.crt), the sandbox binds /nix but not /etc, and --clearenv wipes the daemon's SSL_CERT_FILE. So inside the sandbox pi had no discoverable CA bundle.

Proven in the container: a cleared-env curl https://opencode.ai/ returns http=000; the same env plus SSL_CERT_FILE=<nix ca-bundle> returns http=200. The bundle IS readable inside the sandbox (the wholesale /nix ro-bind covers the nss-cacert store path); pi was simply never told where it is. The sudo/HTTP path is unaffected because it inherits the daemon's full environment.

The fix

sandbox_setenv_args now takes a cert_env slice, filled from the daemon environment by ca_cert_env() / ca_cert_env_from(): SSL_CERT_FILE and NIX_SSL_CERT_FILE (honoured by nix-built TLS clients), each forwarded only when set and non-empty. Both sandboxed paths (interactive pi and sandboxed scripts) pass it, reusing the existing push_setenv emission point.

When the daemon has neither, nothing is forwarded, so hosts that provide a working default cert path keep their current behaviour. These are paths to public root certificates, not secrets, so they are not subject to the RESERVED_SANDBOX_ENV / DAEMON_ONLY_SECRETS guards; those guards are untouched for everything else.

Test

New sandbox_setenv_args_forwards_the_ca_bundle_path_only_when_the_daemon_has_one: asserts --setenv SSL_CERT_FILE <value> appears on the sandboxed path when the daemon has it, that NIX_SSL_CERT_FILE forwards on its own, that an empty daemon value is not injected, and that nothing is emitted when both are unset. All existing sandbox-env tests (reserved-name drop, daemon-only-secret drop, network toggle) stay green. just check passes.

Follow-up (do not do here)

A temporary per-user SSL_CERT_FILE hotfix in [user.env] is currently live in production and is keeping evie up. Once this merges and the image is rebuilt, those per-user cert lines can be removed; this code fix supersedes them. Not attempted in this PR.

## Root cause Sandboxed pi sessions could not make TLS calls to the model provider, so every Telegram turn crashed with `pi provider error: Connection error.` after a ~20s hang. evie's bwrap sandbox does `--clearenv` and then re-adds only an explicit base env allowlist (`PATH`, `EVIE_CHAT_ID`, `EVIE_SOCKET`), which never included a CA-certificate path. Under the old NixOS host, pi's TLS client happened to find a bundle at the default filesystem path the host provided. The nix-built Docker image has no `/etc/ssl/certs/ca-certificates.crt` (only `ca-bundle.crt`), the sandbox binds `/nix` but not `/etc`, and `--clearenv` wipes the daemon's `SSL_CERT_FILE`. So inside the sandbox pi had no discoverable CA bundle. Proven in the container: a cleared-env `curl https://opencode.ai/` returns `http=000`; the same env plus `SSL_CERT_FILE=<nix ca-bundle>` returns `http=200`. The bundle IS readable inside the sandbox (the wholesale `/nix` ro-bind covers the nss-cacert store path); pi was simply never told where it is. The sudo/HTTP path is unaffected because it inherits the daemon's full environment. ## The fix `sandbox_setenv_args` now takes a `cert_env` slice, filled from the daemon environment by `ca_cert_env()` / `ca_cert_env_from()`: `SSL_CERT_FILE` and `NIX_SSL_CERT_FILE` (honoured by nix-built TLS clients), each forwarded only when set and non-empty. Both sandboxed paths (interactive pi and sandboxed scripts) pass it, reusing the existing `push_setenv` emission point. When the daemon has neither, nothing is forwarded, so hosts that provide a working default cert path keep their current behaviour. These are paths to public root certificates, not secrets, so they are not subject to the `RESERVED_SANDBOX_ENV` / `DAEMON_ONLY_SECRETS` guards; those guards are untouched for everything else. ## Test New `sandbox_setenv_args_forwards_the_ca_bundle_path_only_when_the_daemon_has_one`: asserts `--setenv SSL_CERT_FILE <value>` appears on the sandboxed path when the daemon has it, that `NIX_SSL_CERT_FILE` forwards on its own, that an empty daemon value is not injected, and that nothing is emitted when both are unset. All existing sandbox-env tests (reserved-name drop, daemon-only-secret drop, network toggle) stay green. `just check` passes. ## Follow-up (do not do here) A temporary per-user `SSL_CERT_FILE` hotfix in `[user.env]` is currently live in production and is keeping evie up. Once this merges and the image is rebuilt, those per-user cert lines can be removed; this code fix supersedes them. Not attempted in this PR.
fix(pi): forward SSL_CERT_FILE into the sandbox so provider TLS works in Docker
All checks were successful
CI / check (pull_request) Successful in 1m57s
PR Triage — label changes-requested reviews / triage-review (pull_request) Successful in 1s
8142603b62
The bwrap sandbox does --clearenv and re-adds only an explicit base env
allowlist, which never included a CA-certificate path. On a host whose
default cert path exists that went unnoticed; in the nix-built Docker
image there is no /etc/ssl/certs/ca-certificates.crt and the sandbox
binds /nix but not /etc, so pi had no discoverable CA bundle and every
provider call failed the TLS handshake ("pi provider error: Connection
error."). The bundle itself is readable inside the sandbox via the
wholesale /nix ro-bind; pi was simply never told where it is.

Forward SSL_CERT_FILE and NIX_SSL_CERT_FILE from the daemon environment
into the sandbox base allowlist, but only when set and non-empty, so
hosts that provide a working default path behave exactly as before. Both
are paths to public root certificates, so they are not gated by the
secret guards; those stay intact for everything else.
weiwen merged commit 0060630f87 into main 2026-08-05 02:46:44 +08:00
weiwen deleted branch fm/evie-sandbox-cert-fix 2026-08-05 02:46:44 +08:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
weiwen/evie!87
No description provided.