Skip to content

Secrets on the host

The agent's secrets file — where it lives, what permissions it needs, what happens when a value is missing, and why values never leave the machine.

A secret has two halves. The job in Runjet names what it needs, and the agent on the host fills in the value. This page is that second half; the first — how to add a secret to a job and how it is shown — is on the secrets page.

The values live here and nowhere else in the system. Runjet stores only names, only names travel in a dispatch, and a compromised control plane cannot give up what it never had.

The secrets file

AGENT_SECRETS_FILE in the configuration points at a KEY=value file, usually beside agent.env:

/etc/runjet-agent/secrets
# Comments and blank lines are skipped.
DB_PASSWORD=s3cr3t with spaces is fine
S3_TOKEN=AKIA…

It is read verbatim: the key is trimmed of spaces, the value is taken as it stands after the first =. A secret may begin or end with a space, and trimming it quietly would produce a failure that looks like a wrong password. A line with no = stops the agent, with the line number.

Permissions 0600, owned by root or by the agent's account. At startup the agent checks the mode and logs a warning if the file is readable by others — only it should read the file, and what keeps jobs out of it is an account of its own. A file named in AGENT_SECRETS_FILE that does not exist stops the agent; an unset variable simply means "no file".

The file is read at startup. A new secret takes effect after systemctl restart runjet-agent.

The fallback to the service's environment

A name that is not in the file is looked for by the agent in its own environment — so a secret can be supplied by the systemd unit or by -e on a container without a file at all. The environment is taken once at startup; what a job is given depends on how the agent was configured, not on when its run happened to begin.

Two things are lost that way. The agent cannot tell which of its environment variables are secrets, so it will not warn you about a value too short to mask. And the service's environment is visible to anyone who can read the unit. A file is the better choice where you have one.

When a value is missing

A job that names a secret whose value the agent does not have does not run. The run ends failed with the sentence this agent has no value for DB_PASSWORD — substituting an empty string would be worse than useless: the backup would be written somewhere else, the API call would go through quietly without credentials, and the run would report success.

The agent fills in values after it has reported the run as started, precisely so that a missing secret is a failure with an explanation rather than an orphaned run that a sweep would blame on a silent agent. So a job running on several agents may succeed on one and fail on another — every host has its own secrets.

Short values

Values shorter than five characters are not masked by the agent in the output; for the file, it names them at startup. Why, and why masking is a net rather than a boundary, is in job isolation.