SSH agent forwarding risks, and what to use instead

With ssh -A, your agent stays on your computer, but the remote host gets a socket that talks to it. Root on that host can use the socket to log in anywhere your keys work, for as long as you stay connected. The key itself is not copied. Use ssh -J to pass through a jump host instead.

Updated . Run with OpenSSH 10.3 on macOS and a separate test ssh-agent against OpenSSH 10.0 on Debian 13.

What -A does

Without forwarding, your keys are of no use once you are on a server, so a second hop from there cannot use them. ssh -A solves that by connecting the remote host back to your agent:

ssh -A user@server
echo "$SSH_AUTH_SOCK"
/tmp/ssh-G63yAM532X/agent.1441

That path is a Unix socket on the server. Anything sent to it travels back to the agent on your computer, which signs what is asked and returns the result. The server never receives the private key.

The location depends on the server's version. We saw /tmp/ssh-XXXXXXXXXX/agent.<pid> on OpenSSH 10.0. Version 10.1 moved agent sockets to ~/.ssh/agent/.

To load keys into a local agent first, see ssh-agent and the macOS keychain.

What root on the remote host can do

File permissions protect the socket from other ordinary users, and root ignores those permissions. We tested this on a server while a user was connected with -A. As root:

# find /tmp -type s -name 'agent.*'
/tmp/ssh-G63yAM532X/agent.1441

# SSH_AUTH_SOCK=/tmp/ssh-G63yAM532X/agent.1441 ssh-add -l
256 SHA256:IikDwOZxEooOrmm+d3MXJXlobc7rwEyUrQNIBXHzXaU key (ED25519)

# SSH_AUTH_SOCK=/tmp/ssh-G63yAM532X/agent.1441 ssh user@203.0.113.11 id -un
user

So root used the key to log in to another machine, with no prompt on the user's side. Here is the same picture in full:

  • It can: use every key in your agent, to log in to any server or service that accepts them, including a code hosting account. It can also list your public keys and remove keys from your agent: ssh-add -D run on the server emptied our local agent.
  • It cannot: copy the private key. The agent does not hand it out. A stolen socket stops working once your session ends, and we confirmed the socket disappears at that point.
  • Another ordinary user was refused: Error connecting to agent: Permission denied.

"Root" here includes anyone who has broken into the server and gained root. A login made while you are connected can plant its own key on the target and keep access long after you leave. That is why the short lifetime of the socket gives less comfort than it seems.

The alternative: ProxyJump

Most people use forwarding to get through a bastion. With -J, your computer opens an encrypted connection through the jump host to the final server, and the keys are used from your computer alone:

ssh -J user@bastion user@server

Or in ~/.ssh/config:

Host server
    HostName 10.0.0.5
    ProxyJump user@bastion

In our test the connection worked, $SSH_AUTH_SOCK was empty at the target, and no agent socket existed on the jump host. A compromised bastion gets nothing to use. See ProxyJump and jump hosts.

If you do need forwarding

A typical case is running git pull on a server with your personal credentials. First ask whether a deploy key for that one repository, kept on the server, would do. If you still need forwarding, reduce what it exposes.

Ask before each use: ssh-add -c

ssh-add -c ~/.ssh/id_ed25519

Your agent now asks for confirmation on your own screen each time the key is used, so a surprise request is visible. If you refuse, the login fails:

sign_and_send_pubkey: signing failed for ED25519 "key" from agent: agent refused operation

It needs an ssh-askpass program on your computer, which you may have to install. On macOS we found none by default. Without one, test before you rely on it.

Expose fewer keys

  • Run a separate agent holding only the key you need, and forward that one with ForwardAgent /path/to/agent.sock in the host's block.
  • Limit how long keys stay loaded with ssh-add -t 1h ~/.ssh/id_ed25519.
  • Use a key that opens only what this job needs, and not your main key.
  • Look at destination constraints, ssh-add -h, which limit where a key may be used.

Forward only to named hosts

Host build-box
    ForwardAgent yes

Never set ForwardAgent yes under Host *. It is off by default, and that is right. A single careless ssh to a server you do not fully trust would hand that server your agent for the session.

When it is acceptable

It is a reasonable tradeoff when all of these hold: you trust everyone with root on that host, and you trust the host is not compromised; the session is short; and you could not do the job with -J or a deploy key. Avoid it on shared servers, on hosts exposed to the internet that others manage, and on anything you would not leave your unlocked laptop with.

Turning it off on a server

AllowAgentForwarding no

With this in sshd_config, ssh -A connected normally in our test but no socket appeared. The manual notes that users with shell access can still set up their own forwarders, so treat this as a default that prevents accidents, and not as a hard barrier.

Questions

Is SSH agent forwarding safe?

Only on servers where you trust everyone with root. While you are connected, root there can use your keys to log in elsewhere as you. It cannot copy the private key.

What is the difference between ssh -A and ssh -J?

-A lets the remote host use your agent. -J passes through a jump host to the target, and your keys are used only from your computer, so the jump host gets nothing to use.

How do I disable agent forwarding on a server?

Put AllowAgentForwarding no in sshd_config and reload sshd. Users with a shell can still build their own forwarders, so treat it as a default against accidents.

Sources

More in Securing a server

All guides