devmachine

How it works

SSH: logging in and knowing it is your server

SSH checks both sides with different keys. Your key proves who you are to the server. The server’s host key proves which machine answered you.

Your key: how devmachine logs in

If a machine has a key: in your configuration, devmachine uses that key, and only that key. The key setup makes for you is always used this way.

The reason: a server lets you try only a few times — six, usually — and then hangs up. Offering every key you have could use up those tries on the wrong keys before reaching the right one, and you would get a refusal that makes no sense.

When you pick a key from the SSH agent instead — in setup or machines add, the choice that ends in “(from the SSH agent)” — devmachine records its public half as agent_key: in config.yml. From then on it asks the agent for that one key, the same way key: asks a file for its one key. The private half never leaves the agent; only the public key, which is not a secret, is written down.

A machine with neither key: nor agent_key: — set up before this existed, or edited by hand — falls back to asking the agent for everything it holds, which is the risk a many-key agent (a password manager’s, say) can run into. Add agent_key: to it by hand, set to the line ssh-add -L prints for the key you want. See logging in with your 1Password SSH key.

If the agent no longer holds the recorded key — locked, or a different SSH_AUTH_SOCK — devmachine says so by fingerprint rather than trying every key it can find instead.

Two different programs

JobWhat runsWhy
commands, checks, statsthe Go SSH librarythe CLI controls authentication and reads the output
ssh, moshthe system binaryyou want your terminal, your agent, your tmux — not an imitation

devmachine ssh hands the terminal over: it does nothing differently from running ssh yourself.

Which account

A machine’s user: is its admin account — usually root — used for checking and setting the server up. A workspace has a separate account, and devmachine ssh <workspace> logs into that; with no workspace named, it logs in as the admin.

The server’s key: how devmachine knows it is your server

The host public key is safe to store; its private half never leaves the server.

First contact is a decision

Before setup offers a client key or password, it reads only the server’s public host key, prints its type and SHA256 fingerprint, and asks whether to trust it. Compare that fingerprint with the provider console when you need to be sure the address belongs to the right machine.

Accepting without comparing is trusting on faith that the first server you reached is the right one. Once accepted, devmachine remembers that key, so a different server can never swap in later without a warning — but faith alone does not prove the first server was the right one. Refusing stops before any login or remote write.

One store for every connection

Approved keys live in <config>/known_hosts, in OpenSSH format, indexed by machine name rather than address, so a Tailscale address and a public fallback must present the same identity.

Every command reads that file — ssh, mosh, login, tunnel, doctor, setup, generated aliases — checking strictly and only accepting the pinned key. Once a key is stored, every command requires that exact identity before it offers authentication — a missing or different key fails closed instead of being learned silently. devmachine checks it first with a clear error on mismatch, then the system SSH process checks again before connecting, so nothing can swap the key in between.

known_hosts holds public keys and is committed by devmachine setup git. Private keys under keys/ stay ignored and guarded from commits.

Existing configurations

Configurations from v0.6 and earlier have no pin. Commands do not learn one silently — they stop and name the fix:

devmachine machines trust <machine>

This reads the presented key without authenticating, prints its type and fingerprint, and asks before writing. --check compares without writing.

A changed key is not automatically a rotation

A mismatch can mean a deliberate rebuild, the wrong address, or an attack. devmachine cannot tell those apart, so it prints both fingerprints and refuses. Verify the new fingerprint outside this SSH connection first, then record a deliberate rebuild explicitly:

devmachine machines trust <machine> --replace

devmachine machines trust <machine> --check shows both fingerprints without writing, together with a command that prints the server’s own key of the same type. That command is meant for the machine’s console, not for an SSH session: an attacker in the middle would answer an SSH session with whatever fingerprint you expect. An app built on the CLI reads the same report from --format json and should ask for that comparison before it offers to replace the key.

--yes only skips the local confirmation — it cannot replace a key without --replace, and never touches the server. UpdateHostKeys is off, so a server can never rotate this decision on its own.

Removing a machine from config.yml leaves its trust entry in place. Reusing the name must reach the same identity, or go through the same explicit replacement.