november 2026 · 5 min

Agent Forwarding Assumed You Trust the Box

ChatGPT Desktop's Remote SSH can forward your local SSH agent to the machine where Codex runs. When it does, whatever Codex runs there can authenticate as you, to anything your keys open, for as long as the connection is up.

Agent forwarding was designed for a person on a server they trust. Put an agent on the far end and the trust assumption flips: the remote host is now the least trusted thing in the chain, and it holds your keys.

The Mechanism

Sam Labbe's write-up, discussed on HN on Nov 9, traces it to a --forward-ssh-agent flag on the Codex app server that ChatGPT Desktop runs on the remote host. The app server runs with SSH_AUTH_SOCK pointing at ~/.codex/app-server-control/forwarded-ssh-agent.sock, and every process it spawns inherits that. Labbe puts it plainly: those processes "can sign challenges with your keys, silently, because that is what the feature is for."

An open Codex issue, filed Nov 5, describes the same plumbing from the other side: the desktop bootstrap links the forwarded socket to a stable path under the Codex home. In the reporter's setup, with more than one client connected to a host, each new connection repointed that link, so the last client to connect decided whose agent answered.

OpenAI's Remote SSH announcement says the app "automatically detects hosts from your SSH configuration". It doesn't mention agent forwarding, and neither it nor Labbe's write-up says when forwarding is on or how to turn it off.

Why This Is Different From Forwarding to a Server

The OpenSSH ssh_config man page has warned about this for decades: "Agent forwarding should be enabled with caution." The risk it names is users on the remote host who can bypass file permissions on the agent's socket. They can't take the key material, but they can use your keys to authenticate while you're connected.

For a human, that threat model means "someone with root on the box." You forward to a bastion you run, you finish, you disconnect.

For an agent, the process using the socket is the agent itself, and everything it starts. That's the npm install it ran from a README, the test suite of the repo it just cloned, the MCP server it decided to try. None of them need to bypass any permission. They run as you, next to a socket that signs for you. Prompt injection used to be bounded by what the agent could reach on the host. With your agent forwarded, it reaches everything your keys reach, including every private repo you can push to.

VS Code's Remote SSH took the other default. In vscode-remote-release #1222, a maintainer says the agent is never forwarded unless you set ForwardAgent in your own SSH config.

The Check

Open a terminal in a Codex session on the remote host:

echo "$SSH_AUTH_SOCK"
ssh-add -l
ssh -T git@github.com

"The agent has no identities" or no agent at all is the result you want. If ssh-add -l lists the keys from your laptop, the session has your agent, and the last line shows what that means: GitHub answers "Hi ! You've successfully authenticated, but GitHub does not provide shell access." So would any script Codex runs there.

The Control

Give the remote host its own identity instead of lending it yours. A GitHub deploy key is scoped to one repository, and you can make it read-only. Generate it on the host, attach it to the repo the agent works on, and the worst case becomes that one repo.

Then make your own keys useless to a remote process. A FIDO-backed key (ssh-keygen -t ed25519-sk) asks for a physical touch for every signature by default. A script on a remote host can't press a button on your desk, so a forwarded sk key is useless to it while you're away from the key. The touch doesn't show what you're signing, so a touch request you didn't expect is a reason not to touch it. If you can't move to hardware keys, ssh-add -c makes the agent ask for confirmation on your machine for every use, provided you have an ssh-askpass program installed. OpenSSH 8.9 added destination constraints (ssh-add -h) to restrict which hosts a key may authenticate to, and through which hops. The same page recommends ProxyJump over forwarding.

The Tradeoff

Touch-per-signature breaks unattended work. That's intended: an agent running for an hour shouldn't need your personal key, so give it the deploy key. Deploy keys are per repository, so an agent working across five repos means five keys to create and rotate, or a machine user with its own scoped access. And none of this stops the app from forwarding the socket. It only makes what comes through it worth less. Until OpenAI documents when this happens and how to switch it off, assume the remote host gets your agent.

I think this is the pattern behind last week's Dispatch extension attack too. We hand an agent a human-era primitive (an SSH agent, a login session, an approval dialog) and the primitive keeps doing exactly what it was built for, for the wrong principal. My bet: agents will need their own identity, scoped to the task and issued per session, not a pipe into the human's keychain. Until then, don't forward anything you wouldn't hand to the code your agent is about to run.

← all posts