Skip to content

fix(git-ssh-sign): cross-check GitHub signing keys before picking one - #237

Open
mikesir87 wants to merge 1 commit into
docker:mainfrom
mikesir87:fix/git-ssh-sign-github-key-crosscheck
Open

fix(git-ssh-sign): cross-check GitHub signing keys before picking one#237
mikesir87 wants to merge 1 commit into
docker:mainfrom
mikesir87:fix/git-ssh-sign-github-key-crosscheck

Conversation

@mikesir87

Copy link
Copy Markdown
Member

Summary

  • ssh-signing-key-command always signed with whichever key ssh-add -L listed first. A host agent forwarding more than one key (e.g. a password manager's own key alongside the one actually registered on GitHub) can make that the wrong one — the commit signs fine and verifies locally against allowed_signers (which the script itself writes from the same pick), but shows Unverified on GitHub because that key was never added to the committer's account.
  • When the repo's remote is on github.com and gh is authenticated, the command now cross-checks the agent's keys against GET /users/{username}/ssh_signing_keys — the public, unauthenticated signing-keys endpoint — and prefers whichever agent key actually matches, falling back to the first key (with a stderr warning) if none do.
  • Deliberately not github.com/{username}.keys: that only lists authentication keys and can diverge from the signing-key list.
  • The GitHub response is cached for 5 minutes in github-signing-keys.cache next to allowed_signers, since this command runs on every commit/tag signature and unauthenticated GitHub API calls are capped at 60/hour per source IP.
  • On any other remote host, or without gh, this is skipped entirely — no network call, same first-key behavior as before.

Spec choices worth flagging for review

  • Added permissions.network.allow: [api.github.com] — required for the cross-check to run under deny-all. It's a permission grant, not a mandate; non-GitHub repos never trigger a request to it.
  • GitHub Enterprise Server and other forges aren't cross-checked — only literal github.com remotes trigger the lookup. Scoped narrowly on purpose rather than guessing at enterprise hostnames.
  • Username resolution goes through gh api user, so it depends on gh being installed and authenticated (gh auth status). Sandboxes without gh silently fall back to prior behavior.

Test plan

  • go run ./scripts/verify-kit-spec git-ssh-sign — valid, no warnings.
  • ../scripts/test-kit.sh git-ssh-sign — TCK suite passes (validation, network policy, and a real container run of the install hook).
  • Manually extracted the script and ran it against a scratch repo with a github.com remote and one with a gitlab.com remote — the GitLab repo produces byte-identical output to the old script (no cache file, no network call). Unit-tested the key-matching awk against synthetic agent/GitHub key lists for both the match and no-match (fallback + warning) branches, and manually backdated the cache file to confirm it's refetched after the 5-minute TTL and reused within it (verified via a stubbed gh).
  • Not run: ../scripts/test-kit-e2e.sh (needs DOCKERHUB_USERNAME/DOCKERHUB_TOKEN, per CONTRIBUTING.md) and sbx run --kit . (no sbx binary available in this environment).

Origin

Found while debugging why a Docker Learn sandbox's commits showed Unverified on GitHub — the agent's forwarded SSH agent had two keys, and this kit picked the one that isn't registered on GitHub at all.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

The key command always signed with whichever key ssh-add -L listed
first. A host agent forwarding more than one key (e.g. a password
manager's own key alongside the one actually registered on GitHub) can
make that the wrong one - the commit signs fine but shows Unverified
on GitHub, since that key was never added to the committer's account.

When the repo's remote is on github.com and gh is authenticated, cross-
check the agent's keys against GET /users/{username}/ssh_signing_keys
(the public signing-keys list - deliberately not .keys, which is
auth-only and can diverge) and prefer whichever key actually matches,
falling back to the first key with a warning if none do. Cached for 5
minutes since this runs on every commit/tag and unauthenticated GitHub
API calls are capped at 60/hour per source IP.

On any other remote, or without gh, this is skipped entirely - no
network call, same first-key behavior as before. GitHub Enterprise
Server isn't covered yet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Michael Irwin <mikesir87@gmail.com>
@mikesir87
mikesir87 requested a review from a team as a code owner August 25, 2026 16:16
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