fix(signing): register active signing key in allowed_signers so verify-commit passes#597
Merged
dollspace-gay merged 1 commit intoMay 12, 2026
Conversation
…y-commit passes (#737, GH#585) GH#585 reported that in a driver workspace, `crosslink trust approve <agent-id>` only registers the agent's key in `allowed_signers` — the driver's own SSH signing key (which `configure_signing` correctly sets as the cache worktree's `user.signingkey`) is never added. Every subsequent signed hub commit is therefore correctly signed by the driver's key but fails `git verify-commit` because the verifier doesn't trust that key. `crosslink sync` reports the workspace as having "N invalid signature(s) in last N commit(s)" immediately after `trust approve`. The natural diagnostic (`crosslink integrity sign-backfill`) reports "no unsigned entries — nothing to do" because the commits ARE signed, just not trusted. ## Fix (Option B from the issue) Self-healing invariant in `configure_signing`. After any of its three branches (agent worktree, driver workspace, driver-fallback-to-agent) selects a key and calls `signing::configure_git_ssh_signing`, it now also calls `register_active_key_as_trusted` which: 1. Reads the `<key>.pub` companion of the just-selected private key. 2. Loads `allowed_signers` and checks for the key body via the new `AllowedSigners::contains_key` (compares `<type> <base64>` only — matches across different principals and trailing comments). 3. Idempotent: returns early when already trusted under any principal. 4. Appends a new entry using the agent.json identity as the principal (`<agent_id>@crosslink`, mirroring `trust approve`'s format), falling back to `driver@crosslink` if no agent.json exists. 5. Commits unsigned (best-effort) — the new key may not yet be in any earlier commit's allowed_signers view, so signing this commit would re-trigger the very verify-commit failure we're fixing. Same chicken-and-egg navigation as `publish_agent_key`. Option B covers both the GH#585 fresh-bootstrap case AND the driver- key-rotation / re-init case the issue author flagged as the reason to prefer B over Option A's targeted patch in `trust approve`. ## Tests 8 new unit tests: - 5 in `signing.rs` for `AllowedSigners::contains_key`: exact match, ignores trailing comment / different principal under same body, rejects different body, rejects different key type, empty signers. - 3 in `sync/tests.rs` for `configure_signing`: registers the driver key in `allowed_signers` (with explicit `user.signingkey` override in the test repo so `driver_signing_key()` returns the test's fake key instead of leaking the host's real signingkey), idempotency across 3 successive calls (entry appears exactly once), graceful no-op when the `.pub` companion is missing. ## Verification - `cargo fmt --check` clean - `cargo clippy --lib --bin crosslink --tests` clean on touched files - `cargo test --bin crosslink` — 2850 passed (2842 baseline + 8 new) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes GH#585. In a driver workspace,
crosslink trust approve <agent-id>registered only the agent's key inallowed_signers. The driver's own SSH signing key — whichconfigure_signingcorrectly set as the cache worktree'suser.signingkey— was never added. Every signed hub commit from that workspace was therefore correctly signed by the driver's key but failedgit verify-commitbecause the verifier didn't trust that key.crosslink syncimmediately reported "N invalid signature(s) in last N commit(s)" after the very firsttrust approve. The natural diagnostic (crosslink integrity sign-backfill) reported "no unsigned entries — nothing to do" because the commits ARE signed, just not trusted.Fix (Option B from the issue — the author's recommendation)
Self-healing invariant in
configure_signing. After any of its three branches (agent worktree, driver workspace, driver-fallback-to-agent) selects a key and callssigning::configure_git_ssh_signing, it now also callsregister_active_key_as_trustedwhich:<key>.pubcompanion of the just-selected private key.allowed_signersand dedupes via the newAllowedSigners::contains_key(compares by<type> <base64>body — matches across different principals and trailing comments).agent.jsonidentity as the principal (<agent_id>@crosslink, mirroringtrust approve's format), falling back todriver@crosslinkwhen noagent.jsonexists.allowed_signersview, so signing this commit would re-trigger the very verify-commit failure we're fixing. Same navigation aspublish_agent_key.Why Option B over A (which was the smaller-diff alternative — just patch
trust approve): Option B covers both the fresh-bootstrap case AND the driver-key-rotation / re-init case the author flagged as B's structural advantage. Every signing-key selection self-heals.Test plan
cargo fmt --check— cleancargo clippy --lib --bin crosslink --tests— clean on touched files (pre-existing tests.rs warnings at lines 1086/1087/2538/2548 are unrelated)cargo test --bin crosslink— 2850 passed (2842 baseline + 8 new)AllowedSigners::contains_key(exact, trailing-comment-ignored, rejects different body/type, empty signers); 3 forconfigure_signing(registers driver key with explicituser.signingkeyoverride in the test repo so the test doesn't leak the host's real signingkey; idempotency across 3 successive calls; graceful no-op when.pubis missing)init→agent init→trust approve <agent>→quick "test"→sync) on a fresh repo, confirmsyncnow reports "all N recent commit(s) are signed" instead of "1 invalid signature(s)"user.signingkeyin the main repo, re-run any command that triggersconfigure_signing, confirm the new key gets added toallowed_signerswithout needing a manual edit🤖 Generated with Claude Code