Skip to content

ARD-0045: Secrets reach the container via the compose env channel, not --remote-env

Status: Accepted Date: 2026-08-05 Deciders: Tom Steigerwald

Decision

Resolved secret:// values, and git-auth's GH_TOKEN / GIT_CONFIG_*, are exported into boring's own process env and emitted into the dev service's compose environment: block as KEY: "${KEY}" placeholders. Docker Compose interpolates them at up time. --remote-env is no longer used for any secret; the non-secret stamps (BORING_PROFILE_NAME, BORING_HOST_USER) are unaffected.

This mirrors the BORING_EGRESS_MODE precedent already in lib/compose.sh.

Rationale

Two problems, one mechanism.

Argv exposure (#41). devcontainer up --remote-env KEY=VALUE puts every resolved secret in host process argv, readable by any local process for the duration of bring-up. ARD-0044 raised the stakes by putting a push-capable GH_TOKEN on that channel by default.

Secrets never reached most of the container (found while dogfooding). --remote-env on up only covers lifecycle hooks. Everything boring starts afterwards — dev.command via devcontainer exec, and the boring-ui terminal via docker exec — began with the secret unset. In practice shopify theme dev silently fell back to interactive OAuth device-code, and the boring-ui agent pane started logged-out, both of which read as "boring is broken" rather than "a credential is missing". Compose environment: is service env, so it is inherited by every later exec.

--secrets-file (devcontainer CLI 0.87) fixes only the first problem and makes the second worse — those values are explicitly not persisted to later exec.

Consequences

Positive. No secret in argv. No secret on disk — only the ${KEY} placeholder is written to docker-compose.yml. Every exec path sees the same env, so the dev command and the agent pane work without per-path plumbing.

Negative. Values are now visible via docker inspect on the container config, and in boring's own process env (ps e). Both require host access boring already trusts, but it is a different exposure shape than before, not strictly smaller.

Neutral. Compose substitutes in a single pass, so a value that legibly contains $ — git-auth's credential helper embeds a literal $GH_TOKEN for in-container expansion — is emitted as-is. These keys must therefore bypass the generic env_block, which $-doubles literals to suppress interpolation.

Residual, unchanged. Compose env is present in postCreateCommand / setup: hook env, so a profile-authored hook that dumps env still lands secrets on the container filesystem. The profile is the trust anchor (ARD-0006).

Alternatives Considered

  • Keep --remote-env and add it to every exec path (plus docker exec -e for ttyd). Fixes the reachability half and entrenches the argv exposure #41 exists to remove. Rejected.
  • --secrets-file. Off argv, but not inherited by exec, which breaks in-container git push (ARD-0044) and the dev command. Rejected.
  • Git-auth-only fix. Leaves the rest of the secret channel exposed and inconsistent. Rejected.

Implementation

env_channel_put in lib/core.sh exports the value and records the key in BORING_SECRET_ENV_KEYS; lib/compose.sh emits one placeholder per recorded key. Callers: _cmd_open_resolve_secrets, _cmd_run_resolve_secrets, gitauth_build_env. Key recording must happen before compose_generate, which it does in both cmd_open and cmd_run.

Refs: #41, ARD-0044, ARD-0002.