Skip to content

Trust model

Submitting a change to a host takes membership in two sets at once: people who can SSH to that box, and people who hold the fleet’s shared secret. Everything below follows from that sentence.

The three layers

  1. SSH is the only way onto a box. golemd binds 127.0.0.1:7474 by default, and every deployment golem ships keeps it there. --listen will take any address you hand it; a routable one is a choice, not a thing that happens to you.
  2. golemctl rides the SSH connection you already have. Give it an ssh://[user@]host[:port] target — or an inventory host with an ssh field — and it opens a local forward to the agent’s loopback port and speaks plain HTTP through it. Your ~/.ssh/config applies: an existing ControlMaster makes the forward cost milliseconds.
  3. A shared secret authorizes, even then. golemd reads a token file and requires Authorization: Bearer <token> on every request. A malformed, non-Bearer, absent, or wrong header is one and the same 401, which never says which part of the attempt was wrong.

The SSH session is the encryption and the host authentication. Golem has no TLS of its own and needs none: inside the tunnel there is no on-path attacker to defend against.

The bearer comparison folds over every byte whatever the first mismatch is, so its time depends on the token’s length but not on its bytes — a caller cannot learn the secret one character at a time from response timing. The length guard that runs first does leak the length, and that is accepted (ADR 0042): the token is 32 random bytes minted by the harness, so its length is a property of how golem mints tokens rather than of any particular secret.

Configuring it

On the agent:

Terminal window
golemd --host web \
--listen 127.0.0.1:7474 \
--auth-token-file /etc/golem/token

[auth] token_file = "/etc/golem/token" in golemd.toml says the same thing; the flag wins. The file is root-owned, mode 0600.

On the operator’s machine, golemctl takes the secret from the first of: a host’s inventory token_file, GOLEM_AUTH_TOKEN, or the file named by GOLEM_AUTH_TOKEN_FILE. A per-host token_file overrides the environment, so one fan-out can span hosts holding different secrets.

Secrets in a manifest

A manifest is compiled once and applied to many hosts, so it gets stored, cached, and handed to CI. Secretspec.get "DB_PASSWORD" keeps a password out of it: emetc resolves the value through secretspec at compile time and encrypts it to the fleet’s key, and the manifest carries ciphertext.

, env = [ env "DB_PASSWORD" (Secretspec.get "DB_PASSWORD") ]

Interpolating a secret into a string does not seal the string. The value becomes literal chunks with sealed holes, so a config file with one password in it stays reviewable in the manifest and in golemctl plan while only the password is opaque.

Encryption is deterministic, which is what keeps content addressing meaning what it says: the same secret always produces the same bytes, so an unchanged password is a Noop rather than a rebuild-triggered churn, and a rotated one produces a new content id that re-enacts exactly the units depending on it.

A secret may only be a value. A path, a unit name, a scroll name, or a mode is an identifier, and emetc rejects a secret reaching one rather than silently unsealing it. It also refuses a secret in a lineInFile: that glyph owns one line and not the file it appends to, so it can promise nothing about who may read the result — write it with a file glyph at mode = "0600", which owns the whole file and enforces its mode.

The fleet key rides the same channel as the bearer token: root-owned, mode 0600, named by [secrets] key_file in golemd.toml. emetc reads it from --secret-key or GOLEM_SECRET_KEY_FILE. Rotating it means recompiling every manifest that carries a secret — a manifest sealed to the old key is undecodable by the new one, and golemd reports a key mismatch rather than enacting a stale credential.

Compiling a program that uses a secret needs both provider access and the key, so emetc is no longer hermetic for those programs. One that uses no secret needs neither.

What golem does not do

Confidentiality, host authenticity, and reachability are the infrastructure’s, composed from mechanisms that already exist: SSH, unix domain sockets with filesystem permissions, loopback binds, network segmentation, a mesh VPN. Golem’s own abstractions may provision those — they compile to the four glyphs like anything else — but the agent itself enforces exactly one check.

Signing is not the roadmap. An HMAC or ed25519 scheme defends against an on-path attacker replaying or forging a request, and inside an SSH tunnel there is no on-path. Manifest signing would buy key distribution, a wire-format change, and a verification path to defend a boundary the transport already holds.

Content addressing is not authentication. A content id proves what a scroll is, not who sent it. A changed scroll has a changed id; that makes drift detectable, not callers accountable.

What is missing, plainly

No per-user identity. The gate is deliberately one check, so it establishes that someone authorized did this — never who. Per-user identity, an audit trail of who submitted, and SSO group mapping arrive together, with an authentik-issued token replacing the shared secret. That swap changes the middleware and nothing else: the agent still reads one header and either answers 401 or proceeds.

The local forward port is racy, and the race is not closed. golemctl asks the kernel for a free loopback port, closes it, and hands the number to ssh — which binds it only after it authenticates, 100 ms to seconds on a cold connection, while publishing that number in its world-readable /proc command line the whole time. Three things narrow the window: ExitOnForwardFailure=yes turns a lost bind into an exit rather than a silent no-forward; golemctl requires the port to answer on two probes a quarter-second apart while ssh is still alive; and it re-checks the child once more immediately before the first request. What survives is a local process that reads the port from /proc, binds it while ssh is still authenticating, and then answers every probe: golemctl cannot tell it from a working forward, and will send it the bearer token.

That window is accepted (ADR 0042), because reaching it takes a hostile account on the operator’s own machine — which has already lost the game. Closing it needs a rendezvous only the owner can open: forwarding a unix socket in an operator-only directory rather than a TCP port. That is the recorded path, not yet the code.

Rotating the secret

Rotation is rewriting one file and restarting the agents, and its footgun is that a half-finished rotation locks you out of every host you have not reached yet — golemctl holds the new secret, those agents still require the old one, and they answer 401.

So rotate the whole set in one pass, and treat “some hosts return 401” as an unfinished rotation rather than a broken host:

  1. Write the new secret to every agent’s token file.
  2. Restart every agent — the token is read once, at startup.
  3. Publish the new secret to the operators (or update each inventory’s token_file).

An agent whose token file is unreadable or empty refuses to start rather than starting ungated, so a mis-provisioned rotation fails loudly instead of quietly opening a box.

See Status for the full implemented-vs-roadmap breakdown.