Files
hq/02-DECISIONS/0086-a-secret-reaches-a-process-as-a-file.md
T
jschoubben ec7b6e0748 ADR 0086: a secret reaches a process as a file, and an exception is declared
Closes issue 041 by decision and by code on the same branch: the catalogue
engine refuses a secret in a container's env, and a secret-carrying env-file
unless the container declares its reason; the controller reads all six of
its credentials from files; design 13 states the rule and how it is checked.
2026-09-21 10:10:33 +02:00

3.9 KiB

topic, status, date, deciders, reconstructed, extends
topic status date deciders reconstructed extends
building it accepted 2026-09-21 jochen false 02-DECISIONS/0085-a-secret-is-a-provision.md

86. A secret reaches a process as a file, and an exception is declared

Context

The mesh seals a secret to the machine that uses it and discards the plaintext; the host unseals it into a file at 0600 (ADR 0048). Then the module hands it to its container through an env-file, and the runtime puts it where anything on the machine that can talk to the runtime can read it: docker inspect prints it, and /proc/<pid>/environ holds it for the life of the process (issue 041).

Two facts made this a decision rather than a fix. It is a supported shape: placing ${secret:…} in a file a container reads as its environment is what playbook 06 shows, and 36 containers in 25 catalogue modules do it, the controller's own among them. And nothing says which secrets are exposed: a reader cannot tell from a manifest whether a credential is protected from inspect or not, because the manifest looks the same either way. The vault (ADR 0085) rests on the seal this undoes.

Considered Options

  1. Leave it: the environment is where configuration goes. Rejected — it gives the sealed value back to a routine operation, and the care taken to seal it is then theatre.
  2. Refuse every secret in an environment. Rejected — some software reads its configuration from the environment and nothing else, and a rule the catalogue cannot obey is a rule that gets switched off.
  3. A secret reaches a process as a file; an environment exception is declared, with a reason, and refused otherwise. Adopted.

Decision

A secret reaches a process as a file. A module mounts the file the host wrote and points the program at it; the mesh's own programs accept a _FILE twin for every variable that carries a credential, the way the controller's store connections already did.

A container that reads a secret from its environment says so. The manifest key secrets-in-environment on the container carries the reason. It is catalogue-level — the host never sees it — and it exists so a reader can tell from the manifest which secrets are exposed that way and why.

Everything else is refused. A ${secret:…} placeholder inside a container's env is never filled and is refused outright. A file carrying a secret that a container names in env-file is refused unless the container declares the exception.

Consequences

The property a sealed credential is readable only where it is used becomes a manifest-level fact: true where no exception is declared, stated where one is. The controller reads all six of its credentials from files; the 35 other containers are marked with their reason and convert one by one where their software accepts a path, which is per-module work under issue 041.

What got harder: a module author meets one more refusal, and the reason they write is only as honest as they are. What is not decided: the reach of the exposure on a node — which identities can talk to the runtime — which decides whether a declared exception is a hardening item or something sharper.

How it is checked

The catalogue engine refuses at composition, before anything reaches a machine, and its tests refuse both shapes and prove the reason is stripped from what is sent.

References