fix(egress-proxy): chmod 644 host CAs so mitmproxy user can read after docker cp
test / unit (pull_request) Successful in 18s
test / integration (pull_request) Successful in 1m3s

mitmdump crashed at boot with PermissionError on
~/.mitmproxy/mitmproxy-ca.pem. Cause: `docker cp` preserves the
host file's mode AND uid. The CA files were 0600 owned by the host
user (uid 501 on macOS), so inside the container the mitmproxy
user (uid 1000, set by USER directive in Dockerfile) couldn't read
them.

Fix:
  - `egress_proxy_tls_init`: chmod 644 the cert-only + the cert+key
    concat on the host stage dir.
  - `DockerEgressProxy.start`: chmod 644 routes.yaml and the
    pipelock CA before `docker cp` into the egress-proxy container
    (pipelock itself runs as root so its in-pipelock copy is
    unaffected).

The host stage_dir is mode 700 — other host users still can't
traverse in, so the cert+key concat isn't actually exposed despite
the 644 mode. The container side gets world-readable, which is
fine inside the per-bottle container.

Reproduces against today's main: bottle's egress-proxy sidecar
crashes with PermissionError; after this patch mitmdump boots and
listens on :9099.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 15:42:51 -04:00
parent f04fbb68a9
commit 57a9707e1c
+19 -2
View File
@@ -115,11 +115,17 @@ def egress_proxy_tls_init(stage_dir: Path) -> tuple[Path, Path]:
key = work / "ca-key.pem"
if not cert.is_file() or not key.is_file():
die(f"egress-proxy tls init did not produce ca files in {work}")
cert.chmod(0o600)
# Mode 644 (not 600) so `docker cp` preserves world-readability
# inside the container — the mitmproxy user (uid 1000) needs to
# read the file, and the host uid `docker cp` propagates from the
# source doesn't match. The host stage_dir is mode 700 so other
# host users still can't traverse in; the private key isn't
# exposed despite the file mode.
cert.chmod(0o644)
# mitmproxy reads cert + key from a single concatenated PEM file.
mitm = work / "mitmproxy-ca.pem"
mitm.write_bytes(cert.read_bytes() + key.read_bytes())
mitm.chmod(0o600)
mitm.chmod(0o644)
return (mitm, cert)
@@ -232,6 +238,17 @@ class DockerEgressProxy(EgressProxy):
f"{create_result.stderr.strip()}"
)
# routes.yaml also lands inside the container; bump to 644
# for the same reason as the CAs — mitmproxy user (uid 1000)
# has to read it. Host stage_dir is mode 700 so the file
# isn't actually exposed to other host users.
plan.routes_path.chmod(0o644)
# Pipelock CA: pipelock itself runs as root so its in-pipelock
# copy doesn't care about mode, but egress-proxy's mitmproxy
# user does. Bump on the host so docker cp into egress-proxy
# carries world-readable.
if route_via_pipelock:
plan.pipelock_ca_host_path.chmod(0o644)
cps: list[tuple[Path, str, str]] = [
(plan.routes_path, EGRESS_PROXY_ROUTES_IN_CONTAINER, "routes.yaml"),
(plan.mitmproxy_ca_host_path, EGRESS_PROXY_CA_IN_CONTAINER, "mitmproxy CA"),