dev
← All ADRs

Enterprise Network Proxy

ACCEPTEDEngine2026-09-24

Context

autograph containers need outbound internet access for two purposes:

  1. Build time -- npm install -g opencode-ai in the Dockerfile
  2. Runtime -- LLM API calls (HTTPS), package installs from shell, registry access for the root project

In enterprise environments, direct internet access is restricted. Outbound traffic must traverse a corporate HTTP(S) proxy, TLS connections may be intercepted (custom CA certificates), and packages may come from private registries.

The current architecture has no proxy support. The Dockerfile assumes direct npm access. Agent containers have no proxy variables. No mechanism exists to inject custom CA certificates.

Beyond enterprise compatibility, a dedicated proxy container serves two strategic purposes for autograph itself:

  • Centralized outbound interface -- all egress traffic passes through a single, controllable point. This decouples containers from the host's network configuration (corporate proxy, direct access, VPN -- the proxy adapts, the containers don't change).
  • Centralized security enforcement -- the proxy is the natural place to implement egress filtering. Today: pass-through. Tomorrow: per-source allowlists controlling which domains each agent (or the LLM) is allowed to reach, limiting exposure to the internet and the corporate intranet.

Architecture decision already made

A dedicated proxy container (autograph.pkg.proxy) serves as the sole outbound gateway. This ADR resolves the remaining unknowns:

  • Which proxy technology?
  • How does the network topology work?
  • How are CA certificates handled?
  • How is private registry access handled? (generalized -- npm only for now)
  • What are the network isolation boundaries?

Constraints

  • The proxy is always active (no optional profile). It acts as a transparent pass-through when no corporate proxy is configured.
  • Corporate configuration (proxy URLs, CA certs, registry credentials) must never appear in agent workspaces or agent-accessible environment variables. This is a security requirement from the 3-layer model.
  • The solution must work identically in direct-access environments (proxy as pass-through) and behind corporate proxies.
  • Minimal resource footprint -- the infrastructure stack is already substantial (agents, phoenix, west). Every new container must justify its CPU and memory cost.

Decision 1: Proxy technology

Evaluation

Criterion squid tinyproxy nginx (forward proxy) envoy
Image size (Alpine) ~15-20 MB ~2 MB ~25 MB ~50+ MB
Idle CPU/memory ~5-10 MB RSS, negligible CPU ~1-2 MB RSS, negligible CPU ~5 MB RSS, negligible CPU ~30-50 MB RSS, non-trivial CPU
HTTPS CONNECT Native, robust Native Not native (stream + ssl_preread) Native
Upstream proxy chaining cache_peer with auth upstream directive Not native Native (cluster + route)
Custom CA injection ssl_bump (MITM) or pass-through Pass-through only Complex Native (SDS/filesystem)
Egress ACL / filtering Powerful (domain, IP, time, regex). Domain filtering on HTTPS via ssl_bump --peek (reads SNI without full interception). Basic (IP-based Allow/Deny). No domain filtering on CONNECT. Location-based (HTTP only) Powerful (Lua, RBAC, WASM). Native SNI-based filtering.
Logging Detailed (access.log, cache.log) Basic (connect.log) access_log (limited for CONNECT) Structured (JSON, gRPC tap)
Registry auth (transparent) URL rewrite + header injection (HTTP). ssl_bump required for HTTPS. Not supported proxy_set_header (HTTP only) Header injection (Lua filter)
Config complexity Medium (verbose, well-documented) Low (10-15 lines) High (forward proxy is a non-native use) High (YAML/protobuf, complex)
Maintenance / community Active project, security patches Minimal dev activity Very active (but forward proxy is niche) Very active, complex upgrades

Option A -- tinyproxy

Minimal forward proxy. HTTPS CONNECT support. Ultra-simple configuration.

  • For: Smallest footprint (~2 MB image, ~1 MB RAM). Simplest config. Does exactly what we need for basic forwarding -- HTTP forward and HTTPS CONNECT tunnel. Upstream proxy chaining via upstream directive.
  • Against: No TLS interception (pass-through only). No domain-level ACLs on HTTPS CONNECT -- cannot filter which destinations agents are allowed to reach. No transparent registry auth for HTTPS. Minimal logging. Low maintenance activity (risk of abandonment). Blocks the egress filtering roadmap -- would require full replacement to add per-source domain allowlists.

Option B -- squid

Full-featured caching proxy. Industry standard for corporate environments.

  • For: Battle-tested in enterprise environments. Powerful ACLs -- domain filtering on HTTPS CONNECT via ssl_bump --peek (reads SNI without full MITM, enough for allowlists). Rich logging. Upstream proxy chaining with auth (cache_peer). Can do transparent registry auth via URL rewriting and header injection. Large community, regular security patches. Supports the egress filtering roadmap without replacing the technology.
  • Against: Larger footprint (~15-20 MB image, ~5-10 MB RAM). More complex configuration (squid.conf is its own language). Cache features are unnecessary (disabled in our config). ssl_bump for full HTTPS inspection requires generating dynamic certificates (complex, but --peek mode for SNI-only filtering does not).

Option C -- nginx (stream module)

Web server repurposed as forward proxy using the stream module.

  • For: Very widely known. Excellent HTTP reverse proxy. Active development.
  • Against: Forward proxying is not nginx's design purpose. HTTPS CONNECT requires ngx_stream_ssl_preread_module which is a workaround, not a native feature. No upstream proxy chaining for CONNECT tunnels. Configuration for forward proxy use is non-obvious and poorly documented. Most guides and community knowledge focus on reverse proxying. Not a viable path for egress filtering.

Option D -- envoy

Cloud-native proxy. Designed for service mesh and edge proxy use cases.

  • For: Extremely capable. Native HTTPS CONNECT with SNI-based filtering. Rich observability (metrics, tracing, structured logs). Header manipulation (Lua, WASM). Active development by CNCF. Full egress filtering roadmap support.
  • Against: Largest footprint (~50+ MB image, ~30-50 MB RAM, non-trivial idle CPU). Most complex configuration (YAML/protobuf, listener/filter/cluster model). Massive feature surface -- overkill for a forward proxy. Upgrades can be complex (breaking changes between versions). Designed for service mesh, not desktop dev tooling. Disproportionate resource cost for the current use case.

Decision

squid. Rationale:

  • Enterprise-grade forwarding: native HTTPS CONNECT, upstream proxy chaining with authentication, rich logging. Covers the current use case with margin.
  • Egress filtering roadmap: ssl_bump --peek reads the SNI (destination domain) from HTTPS CONNECT requests without full TLS interception. This enables per-source domain allowlists (e.g., agent X can only reach api.anthropic.com and api.openai.com) without generating dynamic certificates or performing MITM. This is a security must-have for the medium term.
  • Reasonable resource cost: ~15-20 MB image, ~5-10 MB idle RAM, negligible CPU in forwarding mode (no caching, no content inspection). Acceptable given the security benefits.
  • Battle-tested: 30+ years in production. Well-understood failure modes. Regular security patches. Enterprise IT teams already know it.
  • Transparent registry auth (future): squid can inject authentication headers for specific upstream domains, enabling transparent access to private registries without exposing credentials to agents.
  • Use an existing image: initially, no custom Dockerfile. An existing squid image (pinned version) with a mounted configuration file is sufficient.

Eliminated alternatives:

  • tinyproxy: blocks the egress filtering roadmap. Would require full replacement when per-source allowlists are needed.
  • nginx: forward proxying is a non-native, poorly documented use case.
  • envoy: disproportionate resource cost and complexity for a forward proxy.

Decision 2: Network topology -- proxy as bridge

The isolation problem

The 3-layer security model requires agent isolation: dev-agent and metalab-agent must not be able to reach each other or see each other's traffic. If both agents join a shared network for internet access, this isolation is broken.

Design

The proxy container joins each agent's isolated network plus a gateway-network that serves as the general-purpose network for all non-agent services. Agents stay on their own networks and never share one.

                       internet
                          |
                      egress-net
                     (proxy only)
                          |
                   +------+------+
                   |    PROXY    |
  +-----------+    +--+---+---+--+    +---------------+
  | dev-agent |       |   |   |       | metalab-agent |
  +-----+-----+       |   |   |       +-------+-------+
        |             |   |   |               |
        +- agent-net -+   |   +- metalab-net -+
          (internal)      |     (internal)
                          |
              +-----------+-----------+
              |      gateway-net      |
              |       (internal)      |
              +--+--------+--------+--+
                 |        |        |
            +--------+ +------+ +-------+
            | west   | | app  | | ...   |
            +--------+ +------+ +-------+

  phoenix is on agent-net + metalab-net (observability) + host-only
  host-only (NOT internal, but no internet) publishes ports to the host:
  phoenix :6006, the seed app dev servers. Temporary -- to be replaced by an
  ingress reverse proxy. host-internet (NOT internal, NAT) is the explicit,
  temporary way to the internet through the host; no container by default.
  All other networks are internal: no Docker NAT, no direct internet. Only
  the proxy is on egress-net (internet for agents).

How it works:

  1. The engine compose defines three internal networks: agent-network, metalab-network, and gateway-network (the compose default). All three have internal: true -- no Docker NAT, no direct internet access.
  2. The proxy package defines a fourth network: egress-network (AUGR_NETWORK_EGRESS). Only the proxy container joins it -- it is the proxy's sole path to the internet. (Two other non-internal networks exist: host-only, to publish ports to the host, and host-internet, a temporary direct way to the internet -- see below.)
  3. The proxy container joins all four networks: the three internal networks (to be reachable by all containers) plus egress (for internet access).
  4. Agent containers stay on their own isolated internal network. They reach the proxy via its hostname (augr-proxy) which resolves on their network because the proxy is a member.
  5. Non-agent services (west, app services) join gateway-network (internal). They reach the proxy there. They also need http_proxy env vars if they need internet access. The seed app containers (front, playground, back) do so, and load the proxy client settings (client.env); they also join host-only to publish their ports (see below).
  6. All containers set http_proxy / https_proxy environment variables pointing to http://augr-proxy:<port>. All outbound traffic goes through the proxy.

Host access (host-only) and direct internet (host-internet): Docker cannot publish a container port to the host from an internal network. The stack (task augr:up) therefore creates two non-internal networks with fixed names (not contract variables), one per purpose:

  • augr-host-only: access to and from the host only, not to the network of the host -- published ports, local development. IP masquerading is off (com.docker.network.bridge.enable_ip_masquerade=false), so traffic to the outside has no way back: no internet. Phoenix (UI on http://localhost:6006) and the seed app containers (dev servers) join it.
  • augr-host-internet: direct internet through the host NAT, for a container that cannot go through the proxy yet. Temporary and explicit: no container joins it by default (for example docker network connect augr-host-internet <container>). Its name sorts before augr-host-only on purpose: on Docker < 28, a container on several networks takes its default route from the first non-internal one by name.

Both are temporary measures, to be replaced by an ingress reverse proxy (see Exx-ingress-local-dns). Until then, host-only no longer gives Phoenix and the app containers a direct way to the internet as a side effect.

Network membership per service:

Service agent-net (internal) metalab-net (internal) gateway-net (internal) egress-net host-only (no internet) host-internet
dev-agent yes
metalab-agent yes
proxy yes yes yes yes
phoenix yes yes yes
west yes
app services yes yes

Key properties:

  • All networks except egress, host-only and host-internet are internal (internal: true in Docker). No agent container has Docker NAT; no agent can reach the internet directly. This is network-level enforcement. host-only is not internal, for port publishing, but has no internet (IP masquerading off).
  • The proxy is the sole internet gateway for the agents. It is the only container on the egress network (its only path to the internet). It bridges the three internal networks to the internet.
  • Proxy down = zero internet for the agents. Guaranteed by the network stack for every container on an internal network. A process that ignores http_proxy env vars still cannot reach the internet. The same holds for the containers on host-only; only host-internet, empty by default, bypasses the proxy.
  • Agents are NOT on a shared network. They cannot reach each other.
  • The proxy does not forward traffic between internal networks -- it only forwards outbound HTTP(S) to the internet.
  • gateway-network is the shared internal network for non-agent services. Services that need internet access set http_proxy pointing to the proxy (same pattern as agents).
  • Host entry point. A client started on the host network sees no Docker network, hence no augr-proxy. The proxy also publishes its port on the loopback of the host only (127.0.0.1:13128, AUGR_PKG_PROXY_HOST_PORT in the pkg.proxy Taskfile; not 3128, often taken by a local corporate proxy). Used by the connect tasks of the agents (OAuth login on the host network, see ADR agent-authentication-architecture, Decision 4).
  • Phoenix is on agent-net and metalab-net for observability, plus host-only to publish its :6006 UI to the host. It does not use the internet (telemetry disabled).

Enforcement summary:

Mechanism What it guarantees
internal: true on all networks but egress and the host ones No agent container has direct internet (no NAT).
IP masquerading off on host-only Published ports without a direct way to internet.
Egress network with only the proxy Single point of internet access.
Proxy on all internal networks All containers can reach the proxy for egress.
http_proxy / https_proxy env vars Applications route through proxy (convention).
Proxy down All internet access stops (verified by test suite).

Build-time vs runtime: two different worlds

The network topology above is runtime only. During docker build, the proxy container may or may not be running. This creates two scenarios:

Runtime (containers running):

  • All outbound traffic routes through the proxy via http_proxy / https_proxy
  • The proxy handles upstream corporate proxy chaining, logging, and (future) egress filtering
  • Full control via squid ACLs

Build time and host tooling (docker build, or a tooling container started before the stack, such as West):

  • The corporate network is a property of the workstation, configured once in env/.env.augr (AUGR_NET_PROXY_URL, AUGR_NET_NO_PROXY, AUGR_NET_CA_CERT_DIR) and declared in the engine contract. squid (its upstream), West (build and run) and the package image builds read this block directly.
  • Image builds receive the proxy as the standard Docker build arguments (http_proxy, https_proxy, no_proxy and their uppercase forms) from the block: on the WSL2 target, a build has no direct route to the internet. The images that make TLS connections (West, agent, seed app) also receive the CA certificates (Decision 3). Predefined proxy arguments are not kept in docker history.
  • autograph does not write the Docker client configuration (~/.docker/config.json).
  • A build cannot go through the stack proxy: BuildKit only builds on the default, host or none networks, not on a stack network.

Rationale: one source for the corporate network, owned by the engine, so that the packages interoperate on any network. Before, the same proxy URL was written in the host shell, in the proxy package env file and in the Docker client configuration, and the CA directory lived elsewhere again.

The operator guide and the image convention are in docs/internal/corporate-network.md.

Credential safety at build time. The npm install needs the registry URL and, for an authenticated registry, a token. The URL is not sensitive; the token must never persist anywhere in the agent image (filesystem, env, layer, or docker history). Options:

Option How Problem
Build arg + same-layer .npmrc cleanup ARG NPM_REGISTRY_TOKEN + rm ~/.npmrc in the RUN --build-arg values are recorded in image metadata and surface via docker history (the legacy builder prefixes the RUN created_by with |N ARG=value). Same-layer cleanup closes only the filesystem vector, not history.
Multi-stage build Use the token only in a discarded builder stage, COPY artifacts to the final stage Keeps the token out of the final image, but does not protect the build cache and adds complexity (copying global npm installs across stages). Not sufficient alone.
Proxy-injected auth (no token in the build) squid injects registry credentials; the agent build only knows the URL Strongest isolation and the long-term target, but transparent HTTPS auth needs ssl_bump (deferred -- see Registry authentication).
BuildKit secret mount (chosen) RUN --mount=type=secret reads the token from a tmpfs mount for one RUN None for the agent image: the token never becomes a build arg, layer, history entry, env var, or part of the final image. Requires BuildKit.

Decision: BuildKit secret mount. It is the only option that keeps the token out of the image and out of docker history without the complexity of multi-stage, and it is available today (unlike proxy-injected auth). Pattern:

# syntax=docker/dockerfile:1
ARG NPM_REGISTRY_URL=""
RUN --mount=type=secret,id=npm_token set -e; \
    NPMRC="$(mktemp)"; export NPM_CONFIG_USERCONFIG="$NPMRC"; \
    if [ -n "$NPM_REGISTRY_URL" ]; then \
      npm config set registry "$NPM_REGISTRY_URL"; \
      if [ -s /run/secrets/npm_token ]; then \
        npm config set "$(echo "$NPM_REGISTRY_URL" | \
          sed 's|^https\?:||')/:_authToken" "$(cat /run/secrets/npm_token)"; \
      fi; \
    fi && \
    npm install -g opencode-ai@${OPENCODE_VERSION} && \
    rm -f "$NPMRC"

The token is supplied via compose build.secrets (sourced from AUGR_PROXY_NPM_REGISTRY_TOKEN); an empty or absent secret means anonymous access. Requires BuildKit -- the default for docker compose build.

Decision 3: CA certificate handling

The problem

In enterprise environments with TLS inspection, the corporate proxy terminates and re-signs TLS connections using a private CA. Containers must trust this CA or all HTTPS connections fail with certificate errors.

Design

Certificate source: one host directory of CA files (.crt or .pem), set once for the workstation with AUGR_NET_CA_CERT_DIR in env/.env.augr (optional). A standard system path (e.g., /usr/local/share/ca-certificates/corporate) lets the host trust the same certificates (update-ca-certificates).

West: it reaches the internet before the stack proxy exists, through the corporate proxy (see "Build-time vs runtime"). The engine copies the directory into the West build context (west:certs) before each build, and the image trusts the certificates for pip (build) and git (run). A volume would not cover the build. The build cache keeps this cheap: only a certificate change rebuilds from that step.

The agent image: it receives the CA the same way, copied by pkg.agent (task certs) into its own build context. On the WSL2 target, its HTTPS download from registry.npmjs.org fails without it (SSL certificate problem). The image also sets NODE_EXTRA_CA_CERTS, which covers opencode at runtime.

The seed app images (node-dev, rust-dev): same mechanism, copied by the app tasks (certs, Taskfile.d/apps/app.yaml); app:rebuild fails on a certificate error without it on the WSL2 target.

Other images (drawio, the proxy): no TLS connection, no CA. A CA step is added to an image only after a certificate error is seen on a target, in the package that owns the image.

Proxy container: squid only tunnels HTTPS (CONNECT) and never validates TLS itself, so it needs no CA certificate.

Agents: their image trusts the corporate CA (above). The trust store is not a workspace file: no corporate configuration appears in agent workspaces.

Known limitation -- corporate TLS inspection on CONNECT tunnels: if the corporate proxy inspects TLS inside CONNECT tunnels, the re-signed certificate reaches the agent (our proxy does CONNECT pass-through), and the agent needs the corporate CA. The WSL2 corporate target inspects TLS (at least registry.npmjs.org, seen in the agent build). The agents get the corporate CA in their image (above), rather than TLS termination in our proxy (see the alternative below); the runtime path is still to be validated.

Alternative considered: inject certs into the proxy only

If the proxy did full TLS interception (squid ssl_bump in bump mode, not just peek), certs would only be needed in the proxy and agents would trust the proxy's own CA. This was rejected because:

  • Full ssl_bump requires the proxy to generate dynamic certificates (complex CA management)
  • It adds a MITM component inside the dev environment
  • It couples agent TLS trust to the proxy implementation
  • ssl_bump --peek (SNI-only, no interception) is sufficient for egress filtering and does not require agents to trust a proxy CA

Decision 4: Private registry access

Scope

This section covers access to private package registries in enterprise environments. The pattern is general (any registry behind authentication), but for now it validates npm specifically because it is the only registry autograph uses today (npm install -g opencode-ai).

Future registries (pip, Docker Hub, Maven, etc.) would follow the same pattern with their own type-specific variables.

Variable naming convention

Registry variables include the registry type in the name to support multiple registry types in the future:

  • AUGR_PROXY_NPM_REGISTRY_URL -- npm registry URL
  • AUGR_PROXY_NPM_REGISTRY_TOKEN -- npm registry auth token

Future additions would follow the pattern:

  • AUGR_PROXY_PIP_REGISTRY_URL, AUGR_PROXY_PIP_REGISTRY_TOKEN
  • AUGR_PROXY_DOCKER_REGISTRY_URL, AUGR_PROXY_DOCKER_REGISTRY_TOKEN

Build-time registry access

The Dockerfile npm install needs to reach the package registry. In enterprise environments, this may be a private registry (Artifactory, Nexus, Verdaccio).

The registry URL (AUGR_PROXY_NPM_REGISTRY_URL) is passed from the env template through the compose file to the Dockerfile as the build arg NPM_REGISTRY_URL (not sensitive). The token (AUGR_PROXY_NPM_REGISTRY_TOKEN) is passed as a BuildKit secret (compose build.secrets -> RUN --mount=type=secret,id=npm_token), never as a build arg (see Decision 2, "Credential safety at build time").

Runtime registry access

Runtime registry access is needed in several scenarios:

  • Agent shell: the operator may want to test an opencode upgrade (npm install -g opencode-ai@latest) from within a running container
  • Root project: the project workspace may need continuous registry access for npm install of project dependencies
  • Future plugins: agents may install plugins at runtime

At runtime, the proxy container handles registry access. Containers reach the registry through the proxy via http_proxy / https_proxy. The registry URL is configured in the container's npm config (set by the entrypoint from AUGR_PROXY_NPM_REGISTRY_URL if non-empty).

Note: the registry URL is set in the container's npm config (visible to the agent), but the auth token is NOT -- it stays in the proxy's configuration (L3). At runtime, the proxy handles authentication transparently (future capability with squid ssl_bump). For now, runtime registry access uses the URL without token-level auth (public registries, or registries with IP-based auth).

Exception (2026-10-19, temporary): the seed app containers. task app:init runs pnpm install inside them, and a corporate registry may require a token. app:prepare writes the registry URL and token in env/ (services/app/npmrc), mounted read-only as ~/.npmrc in the front and playground containers. Risk: those containers run the project code, which agents write; a script run there can read the token. Accepted by the operator until the draft epic Exx-registry-credentials provides a mechanism that keeps the token out of the containers (a registry mirror in the stack, or squid injecting it).

Registry authentication

Current: the registry URL is a build arg; the auth token is a build-time BuildKit secret (never a build arg -- see Decision 2). At runtime, registry access works for public registries or registries with network-level auth. Token-based runtime auth is deferred.

Future: squid can inject authentication headers for specific upstream domains, enabling transparent runtime registry auth without exposing credentials to agents. This requires ssl_bump for HTTPS registries. A registry mirror in the stack is the other candidate. Both are studied in the draft epic Exx-registry-credentials, which also removes the exception for the seed app containers (see "Runtime registry access").

Decision 5: Always-active proxy

The proxy container runs in all profiles (dev, metalab). When no corporate proxy variables are set, it acts as a transparent pass-through -- forwarding requests directly to the internet.

Rationale:

  • Uniform network topology -- services always route through the proxy, whether or not a corporate proxy is upstream
  • No conditional branching in compose files (no "with proxy / without proxy" profiles)
  • Future security use cases (egress filtering, domain allowlists) benefit from always having the proxy in the path -- no retrofit needed
  • Acceptable resource cost: ~15-20 MB image, ~5-10 MB idle RAM, negligible CPU in forwarding mode

Pass-through behavior: when AUGR_NET_PROXY_URL is empty, squid connects directly to the internet (no cache_peer configured). The configuration is identical; only the upstream routing changes.

Consequences

Positive

  • Enterprise unblocked: autograph works behind corporate proxies with custom CAs and private registries
  • Centralized egress control: all outbound traffic passes through a single point. Today: transparent forwarding. Tomorrow: per-source domain allowlists, logging, auditing
  • Agent isolation preserved: no corporate credentials, proxy URLs, or certificates in agent workspaces (L1). Agents on separate networks cannot reach each other.
  • Technology supports the roadmap: squid's ssl_bump --peek enables SNI-based egress filtering without full TLS interception, covering the security hardening roadmap
  • Zero-config for direct access: the proxy is transparent when no corporate config is set. Existing users see no change in behavior.

Negative / Risks

  • Additional container: one more service to start, monitor, and debug. Mitigated by squid's low idle resource consumption and the always-on design (no conditional logic).
  • squid configuration complexity: squid.conf is verbose and has its own DSL. Mitigated by a minimal, well-commented configuration (forwarding only, no caching) with documentation.
  • Proxy is multi-homed: the proxy joins multiple networks (agent, metalab, gateway). While it does not forward traffic between agent networks, a vulnerability in the proxy could theoretically bridge them. Mitigated by squid's maturity and no-new-privileges / cap_drop: ALL hardening.
  • Build-time and runtime are separate mechanisms: host tooling (West) and image builds read the corporate network block of .env.augr directly; at runtime, the stack containers go through the proxy container. Two mechanisms, documented and justified (Docker and BuildKit constraints: a build cannot use a stack network).
  • Build-time credentials use BuildKit secrets: the npm registry token is mounted as a secret for a single RUN, never a build arg -- so it cannot leak via image layers or docker history. The registry URL (non-sensitive) remains a build arg.
  • The corporate CA is baked into the images that need it (West, agent, seed app; when one is configured), each copied by its owner. Other images get a CA step only after a certificate error is seen on a target.
  • Squid image lifecycle changed after this decision: ubuntu/squid:6.10 was based on an unsupported Ubuntu release and no longer received current Squid fixes. pkg.proxy now builds Squid from the Ubuntu 26.04 archive. The container runs as the host UID/GID, drops all Linux capabilities, disables the unused ICMP pinger, and has a package-level rebuild/reup/wait lifecycle.

Open questions

  • Which domains does the WSL2 corporate target inspect? registry.npmjs.org is (agent build). To check from the host for the others (certificate issuer of the domains used by the builds and the agents).

  • Agents at runtime behind the TLS-inspecting proxy: their image trusts the corporate CA; opencode reaching the LLM provider through the tunnel is still to be validated on the WSL2 target.

  • squid image selection: resolved for now by the Ubuntu 26.04 package image built by pkg.proxy. Revisit if a maintained multi-architecture image with a clear Squid-version and security-update contract provides lower maintenance cost.

  • Per-source egress filtering: the design supports it (squid ACLs + ssl_bump --peek), but the configuration model (how operators define allowlists per service) is deferred to Exx-security-hardening.

  • Third-party package egress isolation: services on gateway-network can reach each other. Full isolation requires per-service gateway networks or squid ACLs by source IP. Deferred.