Files
werkator/docs/prs/2026-10-20-PR#166-build-env-off-argv.md
claude-code 9e6dff6bbc
werkator build succeeded after 02:48
werkator/deploy build succeeded after 00:12
werkator/mutation build succeeded after 27:54
The values of a build's variables no longer stand on the command line of werkdock and docker (v1.43.6) (#166)
Co-authored-by: claude-code <claude-code@javagil.de>
2026-10-21 21:26:20 +02:00

144 lines
7.4 KiB
Markdown

# PR#166: The values of a build's variables no longer stand on the command line of werkdock and docker
Version: patch
> **WARNING:** This document describes only the change applied in this PR.
> It may already be outdated once the next PR is merged.
> Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.
## Problem
Werkator passes each variable of a sandboxed build as `-e KEY=VALUE` to `werkdock run`,
and as `--env KEY=VALUE` to `docker run`.
So every secret of a build stands in the argv of that process, e.g. the tokens of the ai-review build of werkrevisor.
Each process that can read `/proc/<pid>/cmdline` sees them, and so does each `ps`, crash report or log of an argv.
On mih09 on 2026-10-20 they leaked from `pgrep -af` into a transcript.
## Non-Goals
- No change of what a build sees: the same variables with the same values, the same precedence of `werkdock.env`/`docker.env`.
- No change of `bwrap`'s command line;
werkdock passes the values on as `bwrap --setenv` up to 0.13.1, and werkdock PR#17 ends that.
- No rotation of the leaked secrets; that is the human's.
## Specification
### Feature: a build's variables reach the sandbox off the command line
#### Background
- werkdock takes a bare `-e KEY` from its own environment
and omits the variable when that has none (since 0.2.0).
- docker reads `--env-file`: one `KEY=VALUE` per line, before the `--env` operands, a later entry winning.
#### Scenario#166.01: the werkdock command line names a build's variables, werkdock's environment holds their values
&hellip; so that no `ps` and no log of an argv shows a secret of a build.
- **Given** a build with a token and a whole configuration file in `werkdock.env`
- **and** a server with a secret of its own in its environment
- **When** Werkator starts the build
- **Then** the argv of `werkdock run` names each variable as a bare `-e KEY`, and holds none of the values
- **and** werkdock's environment holds the values,
and of the server's environment only what werkdock reads itself
##### Verified by
- [WerkdockBuildRunnerUnitTest](../../src/test/kotlin/de/javagil/werkator/build/WerkdockBuildRunnerUnitTest.kt)
- [RuntimeEnvironmentUnitTest](../../src/test/kotlin/de/javagil/werkator/build/RuntimeEnvironmentUnitTest.kt)
#### Scenario#166.02: a variable that steers werkdock itself stays on the command line
&hellip; so that a build cannot change how werkdock runs outside the sandbox.
- **Given** a build that sets `PATH`, `WERKDOCK_HOME` and `LD_PRELOAD` in `werkdock.env`
- **When** Werkator starts the build
- **Then** the command line holds each as `-e KEY=VALUE`, as before
- **and** werkdock runs with the server's values of these three
##### Verified by
- [WerkdockBuildRunnerUnitTest](../../src/test/kotlin/de/javagil/werkator/build/WerkdockBuildRunnerUnitTest.kt)
- [RuntimeEnvironmentUnitTest](../../src/test/kotlin/de/javagil/werkator/build/RuntimeEnvironmentUnitTest.kt)
#### Scenario#166.03: the build still sees the values
&hellip; so that a build that needs its secrets keeps working.
- **Given** a build with a key and an empty value in `werkdock.env`
- **When** Werkator starts the build
- **Then** the command in the sandbox sees both values
- **and** werkdock sees none of the server's own secrets
##### Verified by
- [WerkdockBuildRunnerIntegrationTest](../../src/test/kotlin/de/javagil/werkator/build/WerkdockBuildRunnerIntegrationTest.kt)
#### Scenario#166.04: a Docker build's values reach the container through a file only the server's account reads
&hellip; so that neither `ps` nor a program the docker client starts on the host sees them.
- **Given** a build with a token in `docker.env`
- **When** Werkator starts the build
- **Then** the command line of `docker run` holds no value of the build
- **and** only the server's account reads the file that holds them, and only until the build ends
- **and** a value that docker's file format cannot hold stays on the command line, as before
##### Verified by
- [DockerBuildRunnerUnitTest](../../src/test/kotlin/de/javagil/werkator/build/DockerBuildRunnerUnitTest.kt)
- [DockerEnvFileUnitTest](../../src/test/kotlin/de/javagil/werkator/build/DockerEnvFileUnitTest.kt)
## Solution
- werkdock: `RuntimeEnvironment` splits a build's variables into the `-e` operands and the environment of `werkdock run`,
which `WerkdockBuildRunner` starts with exactly that environment, cleared first.
It holds the server's values of werkdock's own names, then the build's values;
before, werkdock inherited all of the server's environment.
- werkdock's own names: what werkdock, the Go runtime or glibc reads, e.g. `PATH`, `HOME`, `WERKDOCK_HOME`,
`LD_PRELOAD`, `GCONV_PATH`; the full list is in `docs/configuration.md`.
A build variable of such a name keeps `KEY=VALUE` on the command line, as before.
In werkdock's environment it would steer werkdock: a branch could choose the `bwrap` it runs, its image store,
or a library loaded into a process outside the sandbox.
A name with `=` or an empty name stays on the command line, since no environment can hold it.
- The environment route is safe for werkdock alone: `werkdock run` starts no host program but itself and `bwrap`,
and werkdock PR#18 starts `bwrap` with an empty environment.
- Docker: `DockerEnvFile` writes the variables, after the container defaults `HOME` and `GRADLE_USER_HOME`,
to a `0600` file in `.git/werkator/docker-env/` (`0700`), which the build container sees masked.
The file goes when the client ends;
a file older than an hour, left by a killed Werkator, goes with the next Docker build.
The docker client keeps Werkator's environment, as before, and gets no build value in it.
- Why not the environment for Docker too: the Codex review found that the docker client starts host programs with it,
`ssh` for an `ssh://` daemon (`SSH_ASKPASS`) and credential helpers such as `pass` (`BASH_ENV`).
A list of forbidden names never covers every helper; the human chose the file (2026-10-20).
- docker's file format holds one line per variable, shorter than 64 KiB:
a value with a line break, a longer line, or a name docker would misread
stays `--env KEY=VALUE` on the command line, after the file, as before.
- `SecretFiles.createDirectoriesOwnerOnly` takes a directory that another thread created in the meantime,
so two Docker builds that start together both get their file (review of the Codex agent).
- Red/green: with `KEY=VALUE` operands back, the tests of Scenario#166.01 fail.
- Werkator and werkdock can be rolled out in either order; the leak ends with both.
- The native runner already puts the variables into the build's environment,
and the follow-up builds run through the same runners.
- Version 1.43.6 with a release-notes entry.
## Open Questions
- [x] Does anything else of Werkator put a build's value on a command line?
No: the image builds, `werkdock images`/`import` and `curl` carry no build variable.
## Additional Changes
- `docs/configuration.md`, the architecture skill and `docs/werkator.werkbaum` describe the change.
## Prerequisite PRs
- None; werkdock has taken a bare `-e KEY` since 0.2.0.
## Follow-up PRs
- [werkdock PR#17](https://git.javagil.de/mi/werkdock/pulls/17):
bwrap reads the sandbox's environment from a pipe, not from its command line;
an instance's files are readable by the owner only.
- [werkdock PR#18](https://git.javagil.de/mi/werkdock/pulls/18): bwrap starts with an empty environment.