DEV Community

Nic Flemmer
Nic Flemmer

Posted on

Letting a coding agent use SSH without giving it your key

When a coding agent needs to reach a server, the easy options are both bad. You can leave a key where the agent can read it, or you can tell it to use your normal ssh setup and hope it only does what you meant.

I build Styx, a free and open-source desktop app that runs coding agents and makes them ask before they touch production. This is how it handles SSH, what the agent actually gets, and where the protection ends.

A target, not a key

An SSH target in Styx is a host, a user, a port (22 by default), an optional jump host (user@bastion:22, comma-separated for several) and an environment: prod, staging or preview. Agents don't get a special tool. They run plain ssh <host>, scp, sftp or rsync, and Styx applies the target's user, port and jump host for them. If the agent gives itself a user or a port, its choice wins.

That is the 0.4.19 change: connecting SSH servers with their saved user and port, by key, password or your own setup.

Three ways to sign in

Key file. Styx stores the key's path and passphrase in the OS keychain. It does not store the key. Each time a grant is issued it reads the key from disk. The agent never gets the key file: it gets a forwarded SSH agent socket that works for the lifetime of the grant.

Password. The password lives in the keychain. When you grant access, Styx signs in to the server itself and the agent uses that connection, so it never sees the password. This isn't available on Windows, whose ssh can't share a signed-in connection. There you use a key file or your own setup.

My SSH setup. Nothing is stored. ssh uses your own agent and ~/.ssh/config, so 1Password, hardware keys, certificates and keys you already use all keep working. Since Styx stores nothing in this mode, what your own agent and config can reach is up to them.

Test connection signs in the way an agent will

The form has a Test connection button that signs in exactly the way an agent will. If the server only accepts passwords and you chose a key, it says so, and says how to add your key to the server. I'd rather find that out on the form than in the middle of an agent's task.

Asking, and who decides

A connected target doesn't mean the agent can use it. It has to ask, say why, and you choose what it gets: once, for an hour or for the session. For SSH on a prod target the grant hands over your whole credential whatever scope was asked, so Styx turns any auto-approval into an ask with Touch ID, Windows Hello or your system password, reads included. Every request, grant, use and revoke goes into an append-only audit log.

Where it stops

Styx is a guardrail, not a sandbox. Agents run as you, on your machine. The ssh command the agent runs by name hits a shim that asks first, but an agent that calls the real binary by its full path goes around it. Claude Code and Codex get a warning in the chat when Styx sees it, not a block. What Styx guarantees is that it never hands an agent your key and that every grant it gives is scoped, timed and written down.

The code is at https://github.com/NicholasFlemmer/styx-app and the page on connecting targets is https://heystyx.com/docs/access/connect-a-target.

How do you let an agent reach a server today: a separate key, your own agent, or something else?

Top comments (1)

Collapse
 
mickyarun profile image
arun rajkumar •

The Where it stops section is why this is worth reading, so I will take you at your word on it and push on the part that is not in it.

A grant for an hour or for the session bounds time, not actions. The forwarded agent socket is live for the whole window, so one approval buys an unlimited number of connections and an unbounded number of commands. On a prod target that is the same shape as approving a payment and leaving the amount field editable. Touch ID makes the moment feel heavy, and what it authorises is still open-ended.

The cheap improvement short of a sandbox is to make the unit of the grant a connection rather than a window. One ask, one session, dies on disconnect. An rsync of a whole tree is still one approval, and a loop that reconnects forty times is forty, which is the pattern worth noticing.

To your question: a separate key, no agent forwarding, and a host the agent cannot reach anything else from. Not because that is more secure in principle than what you built, but because the bypass you named is honest and general. An agent that can call the real binary by its full path routes around any shim, so I would rather the credential were never reachable than the wrapper were well behaved. The append-only log is the part I would keep either way. A guardrail that writes down what it allowed is worth more than one that claims it cannot be gone around.