Secret handling
Secrets are never written to disk where an agent could read them. They are fetched at the moment of use, held in memory or in a short-lived private file, and cleaned up when the process exits.
This is the part of the system the 2026-05-23 incident was about: an agent saved a credential to the wrong place, and three written rules failed to stop it (see the overview). The fix wasn’t a fourth written rule. It was a design where the convenient way to get a secret and the safe way are the same way — plus checks that make every other way fail.
The broker model
Section titled “The broker model”All secrets live in a vault on a separate machine (prontera). Nothing on the main host
stores them. When a service needs a credential, it presents a token to a small
broker service, and the broker hands back the secret — over the network, at runtime, into memory.
This is HARD RULE 10, invariant INV-024, and decision record ADR 0105 all describing the same
arrangement from three angles (the rule, the test, and the reasoning).
main host prontera ───────── ──────── service needs a secret │ presents its token ▼ broker client ───────────────────▶ vault / broker ▲ │ └────── secret, in memory ──────────┘
on disk on the main host: the token only — short-lived, mode-700 directory, deleted when the process exitsThe ephemeral token
Section titled “The ephemeral token”The one thing that does touch disk is the token itself — and only barely. Two scripts manage its whole life:
scripts/with-ephemeral-runtime-token.sh— fetches the token from wherever it can be found safely (an operator-supplied env file, systemd’s credentials directory, an already-running container, or typed in on stdin), writes it into a freshly created directory only the owner can read (mode 700), runs the wrapped command, and deletes the directory on exit — including on crashes, via a shell exit trap.scripts/compose-up.sh— the only correct way to start the stack. Because the token file is temporary, a stopped container’s bind-mount can point at a path that no longer exists; plaindocker restartthen fails. This script detects those stale containers, removes them, and recreates them with a fresh token via the wrapper above.
Even GitHub access goes through the broker
Section titled “Even GitHub access goes through the broker”scripts/gh-as-soho.sh is the required way to open a pull request. It resolves a GitHub
token through the same broker (trying the host path first, then falling back to asking a running
container to fetch it), checks the token actually belongs to the expected bot account by calling
GitHub’s /user endpoint, and only then runs gh. If any step fails it stops
with an error — it never silently falls back to whatever credentials a bare gh would
find lying around. PRs therefore always carry the right identity, and no long-lived GitHub token
needs to exist on the host.
The blockers — making the wrong way fail
Section titled “The blockers — making the wrong way fail”A safe path only helps if the unsafe paths are closed. Three guards close them:
| Guard | What it blocks |
|---|---|
check-agent-vault-no-local-secret-materialization.py |
Code that would write broker secrets to local files or export them into process environments. It greps the tree for the known dangerous patterns (the old credential-dir variable, the materialization helper, raw-secret-env flags) and fails CI on any hit. |
check-secret-startup-denylist.py |
Startup scripts quietly going back to the legacy secret managers (Infisical, Doppler) that the broker replaced. |
load-agent-vault-env.sh |
The legacy loader itself still exists as a file — but it has been gutted: it prints an error and exits with code 78 (“config error”) no matter how it’s called. Anything still depending on it breaks loudly instead of working unsafely. |
And one audit checks reality rather than code:
check-process-secret-exposure.py inspects the environment and command line of live
processes and containers for secret-shaped values. It reports the location of a leak, never the
value. The difference matters: the other guards prove the code can’t leak; this one proves
the running system currently isn’t.

