SSH ed25519-sk hardware keys vs forwarded agents: when to forward, when to use a YubiKey and refuse forwarding

Most “hopping through bastion” SSH workflows use agent forwarding (ssh -A): the bastion gets temporary access to your local SSH keys via the agent socket, and from there it can SSH onward. It’s convenient, it’s the default in most tutorials, and it’s also the move that loses the most enterprise pentests. The bastion is, briefly, you. If it’s compromised, the attacker has you for the duration of your session.

The hardware-key alternative — ed25519-sk on a YubiKey — eliminates that risk by making the private key non-exportable: even if the bastion is compromised, the attacker can’t move your key elsewhere because the key is on a physical device that requires a touch.

What’s wrong with agent forwarding

When you run ssh -A bastion, the SSH client creates a forwarded agent socket on the bastion. Any process on the bastion that can read your environment can use that socket — which means it can request SSH operations against your local agent, in real time. It can list your keys, sign challenges as you, and SSH onward to anything your local agent has the keys for.

Concrete attack: a bastion you SSH into has been compromised. Attacker is watching for incoming connections with SSH_AUTH_SOCK set. They use your agent to ssh prod-db.internal. You don’t see the connection because it’s coming from the bastion as you, with your keys.

It’s worse than it sounds. The attacker’s window isn’t just the duration of your session — they can keep the auth socket open for as long as they’re sneaky enough not to disconnect it. They can also pull keys from the agent (ssh-add -L works through the forwarded socket), giving them a list of every server you have access to.

What ed25519-sk fixes

The -sk suffix is for “security key” — keys that live on a hardware token (YubiKey 5+, Solo Key, OnlyKey). The private key never leaves the hardware. Every signing operation requires a physical touch (a tap on the YubiKey’s button).

Generate one on your laptop:

ssh-keygen -t ed25519-sk -O resident -O verify-required \
  -C "yubikey-$(hostname)-$(date +%Y%m)"

The flags matter:

  • -O resident — the key is stored on the YubiKey itself, not just referenced. This means you can plug your YubiKey into a different machine, run ssh-keygen -K, and recover the public + private-handle pair. Without resident, the key file is the only handle, and losing it bricks the key.
  • -O verify-required — every use requires PIN entry on the host (the YubiKey will refuse to sign without PIN verification). Combined with the touch requirement, an attacker who steals the key file and physical possession of the YubiKey still needs the PIN.

Copy the public key to your servers’ ~/.ssh/authorized_keys as usual. SSH from then on requires you to be physically present (tap the YubiKey when it blinks). Crucially: even if you forward your agent, a compromised bastion can request signatures, but each one requires a physical tap on the YubiKey. The attacker can’t move silently.

The “I forward, but only to bastions I trust” middle ground

Even with hardware keys, agent forwarding still has a downside: every onward SSH from the bastion makes the YubiKey blink. For interactive jump-then-work workflows it’s fine. For automation that hops through a bastion, it’s painful.

The right pattern: ProxyJump (-J), not -A.

# Old way — bastion has access to your keys via agent socket
ssh -A bastion
ssh prod-host  # uses forwarded agent

# Better — bastion never sees the agent
ssh -J bastion prod-host

# Or in ~/.ssh/config:
Host prod-*
    ProxyJump bastion
    User deploy

With ProxyJump, the bastion just forwards encrypted traffic between your laptop and the destination. Your SSH client authenticates to the destination directly using its local agent — the bastion never sees the keys, never has an agent socket, never can sign on your behalf. The TCP connection passes through; the cryptographic auth doesn’t.

Decision matrix

  • Single-hop SSH from your laptop: Use ed25519-sk. The touch requirement adds 0.5 seconds to each connection and eliminates the entire class of “stolen key file” attacks.
  • Hopping through a bastion: Use ProxyJump, not agent forwarding. Combine with ed25519-sk on the laptop for hardware-rooted auth on every hop.
  • Bastion is itself untrusted (corporate, shared, ephemeral): Definitely no agent forwarding. ProxyJump + hardware key is the only safe combination.
  • You absolutely have to forward an agent (some legacy tool requires it on the bastion): Use ed25519-sk anyway. The hardware-key requirement still protects you — the attacker can request signatures but can’t make them without your physical tap.

The “what if I lose the YubiKey” problem

The cost of hardware keys: lose the device, lose access. The mitigation is to enroll two YubiKeys per device — one in your wallet, one locked in a drawer at home. Both get added to authorized_keys on every server. Lose the wallet one, you can still get in via the spare while you reissue.

YubiKey 5C NFCs are about $50 each. For protecting access to anything more important than a hobby box, two of them is cheap.

One last guardrail

Even if you switch to hardware keys and ProxyJump everywhere, audit your existing keys: ssh-add -L will list every key your local agent currently has loaded. If any are software keys with no hardware backing, retire them — they’re the weak link in an otherwise hardened workflow.

The transition takes an afternoon: generate a new ed25519-sk, push the public key everywhere, retire the old keys, switch to ProxyJump in your SSH config. Worth the effort exactly once.

Cover photo: Cottonbro on Pexels.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.