
Most of the SSH keys protecting servers today are just files.
If you can copy a file, you can copy the key. Whoever ends up with it logs in as you, from anywhere, at any hour. The server sees a valid signature, not a person.
For two decades that was an acceptable trade. Stealing the file took real effort: a stolen laptop, malware that landed, the right backup. Low odds, manageable blast radius.
That math does not hold anymore.
Credentials that travel
We delegate now. AI agents hold credentials, run long sessions, and act on our behalf while we sleep. Every agent that holds a private key is one more place that key can leak from: a logged tool call, a copied context, a prompt that ended up where it should not be.
And attacks got cheap to personalize. We wrote about one: a fake recruiter, a cloned repo, a "test task" that was really malware. That attack asked the victim to run code. With AI doing the writing, the next message will be flawless, and it might simply ask for a file.
A server still cannot tell you from a copy of you.
A key that will not sign without you
A FIDO2 key (a YubiKey, a Nitrokey, a SoloKey, any of them) can hold an SSH private key sealed inside hardware. OpenSSH has supported this since 8.2 through the ed25519-sk key type. The private key never leaves the token; the file on your laptop is just a handle pointing at the credential on the device.
The important part is what the token refuses to do. It will not sign an authentication request unless someone taps it. No tap, no signature, no session.
A key that lives in hardware cannot be synced to the wrong place, pasted into a prompt, or uploaded by accident: there is nothing to copy. Yubico keeps a good guide for generating one:
resident keeps the credential on the token itself, so a new laptop recovers it with ssh-keygen -K instead of moving files around. verify-required (OpenSSH 8.4+) demands a PIN or fingerprint on top of the tap, so a stolen token alone is just plastic.
Our setup, open-sourced
The same idea applies server-side. We published the cloud-init scripts we use on our own VMs (Apache 2.0, Ubuntu and Debian). The SSH one provisions every machine with:
password logins off entirely
root reachable only through public-key auth
PubkeyAuthOptions verify-required, so the server refuses any key that does not prove a PIN or fingerprint was used
That last line is the whole point. The server itself enforces that a human was there. Not a wiki page, not a policy PDF: the daemon checks.
It goes into cloud-init because the lazy path should be the secure path:
Any Ubuntu 22.04 or Debian 12 image ships an sshd new enough for this.
What we recommend
Buy two FIDO2 keys per person. Tokens get lost and snap in USB ports.
Set a PIN on the token before generating keys. No PIN, no
verify-required.Generate
ed25519-skkeys withresidentandverify-required, and enroll both tokens.Bake the server config into provisioning (ours or your own), so the policy is a default, not a checklist.
Expect rollout friction: once the server requires verification, every legacy key stops working. Migrate people first.
Keep a break-glass path: console access that does not depend on the token. Test it before you need it.
One macOS gotcha that will waste an afternoon: the OpenSSH that ships with macOS is built without FIDO support. brew install openssh, and make sure it comes first on PATH.
Final thoughts
Passwords taught us that anything you know can be phished. Key files taught us that anything you possess can be copied. A key sealed in hardware teaches the server something neither can: that the person on the other end is actually there.
In an age where software acts on our behalf, proof of presence is worth paying for.
Author: Guil. Sperb Machado, Ph.D.
CEO & Builder @ AxLabs



