Skip to content

fix(yubikey): drop AddKeysToAgentssh-agent can't re-prompt for the FIDO2 PIN#302

Merged
DevSecNinja merged 1 commit into
mainfrom
fix/yubikey-no-agent-cache
May 4, 2026
Merged

fix(yubikey): drop AddKeysToAgentssh-agent can't re-prompt for the FIDO2 PIN#302
DevSecNinja merged 1 commit into
mainfrom
fix/yubikey-no-agent-cache

Conversation

@DevSecNinja

Copy link
Copy Markdown
Owner

Reported in chat. Sequence on macOS:

$ ssh -T git@github.com
Confirm user presence … Enter PIN … User presence confirmed   ✅

$ ssh -T git@github.com
sign_and_send_pubkey: signing failed for ED25519-SK
"~/.ssh/id_ed25519_sk_<serial>" from agent: agent refused operation
git@github.com: Permission denied (publickey).

Root cause

~/.ssh/config had AddKeysToAgent yes for the YubiKey path. First ssh worked because no key was in the agent yet — OpenSSH talked to the YubiKey directly, did the FIDO2 touch+PIN dance, then cached the handle in ssh-agent. Every subsequent ssh went through the agent, which cannot re-prompt for the FIDO2 PIN (especially on macOS), so the cached entry just refuses every signing op with agent refused operation.

Fix

For the FIDO2 path, set IdentitiesOnly yes and don't ask the agent to cache anything:

 Host *
     User <user>
 {{- if and .useYubiKey $hasSkKey }}
-    AddKeysToAgent yes
+    IdentitiesOnly yes
     # ... IdentityFile lines

The private key never leaves the YubiKey, so "caching it in the agent" only buys us the broken state. OpenSSH talks to the YubiKey directly each time and the touch+PIN dance stays interactive and reliable.

yk-enroll's Done. block updated to describe the new behaviour. Doc gained a Why not AddKeysToAgent yes? section with the verbatim error message and the ssh-add -D recovery for anyone landing on this from the broken state.

Recovery for already-stuck users

ssh-add -D                   # drop all cached keys
chezmoi apply                # picks up new ~/.ssh/config without AddKeysToAgent
ssh -T git@github.com        # works

Verify

./tests/bash/run-tests.sh --test yk-enroll.bats

18/18 green (no test changes needed; tests don't assert on the SSH config wording).

Files

  • home/private_dot_ssh/config.tmplAddKeysToAgent yesIdentitiesOnly yes + comment block explaining why
  • home/dot_config/shell/functions/yk-enroll.sh — updated Done. step-2 wording
  • home/dot_config/fish/functions/yk_enroll.fish — same
  • docs/yubikey.md — new "Why not AddKeysToAgent yes?" section

When SSH config sets 'AddKeysToAgent yes' and the IdentityFile is a

FIDO2 (*-sk) key with verify-required, the first ssh call works (touch

+ PIN, key cached in agent), but every subsequent call fails with:

    sign_and_send_pubkey: signing failed for ED25519-SK "..." from agent:

    agent refused operation

    git@github.com: Permission denied (publickey).

Reason: ssh-agent on macOS (and most platforms) has no UI to re-prompt

for a FIDO2 PIN, so the cached entry refuses every signing op after the

first one. Workaround was 'ssh-add -D' before every push, which is awful.

Fix: for the YubiKey path, set 'IdentitiesOnly yes' instead and don't

ask the agent to cache anything. The FIDO2 private key never leaves the

YubiKey, so 'caching' it in the agent only buys us the broken state.

OpenSSH talks to the YubiKey directly each time and the touch + PIN

dance stays interactive.

yk-enroll's Done. block updated to describe the new behaviour.

Docs: docs/yubikey.md gained a 'Why not AddKeysToAgent yes?' section

with the agent-refused error verbatim and the 'ssh-add -D' recovery.
@DevSecNinja DevSecNinja marked this pull request as ready for review May 4, 2026 10:55
@DevSecNinja DevSecNinja merged commit 9eb385e into main May 4, 2026
13 checks passed
@DevSecNinja DevSecNinja deleted the fix/yubikey-no-agent-cache branch May 4, 2026 10:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant