Skip to content

fix(signing): register active signing key in allowed_signers so verify-commit passes#597

Merged
dollspace-gay merged 1 commit into
developfrom
fix/585-driver-key-register-in-allowed-signers
May 12, 2026
Merged

fix(signing): register active signing key in allowed_signers so verify-commit passes#597
dollspace-gay merged 1 commit into
developfrom
fix/585-driver-key-register-in-allowed-signers

Conversation

@dollspace-gay
Copy link
Copy Markdown

@dollspace-gay dollspace-gay commented May 12, 2026

Summary

Closes GH#585. In a driver workspace, crosslink trust approve <agent-id> registered only the agent's key in allowed_signers. The driver's own SSH signing key — which configure_signing correctly set as the cache worktree's user.signingkey — was never added. Every signed hub commit from that workspace was therefore correctly signed by the driver's key but failed git verify-commit because the verifier didn't trust that key. crosslink sync immediately reported "N invalid signature(s) in last N commit(s)" after the very first trust 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 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 dedupes via the new AllowedSigners::contains_key (compares by <type> <base64> body — matches across different principals and trailing comments).
  3. Idempotent: returns early when the key is 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 when no agent.json exists.
  5. Commits unsigned — chicken-and-egg: 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 navigation as publish_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 — clean
  • cargo 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 crosslink2850 passed (2842 baseline + 8 new)
  • 8 new tests: 5 for AllowedSigners::contains_key (exact, trailing-comment-ignored, rejects different body/type, empty signers); 3 for configure_signing (registers driver key with explicit user.signingkey override in the test repo so the test doesn't leak the host's real signingkey; idempotency across 3 successive calls; graceful no-op when .pub is missing)
  • Manual: reproduce the GH#585 sequence (initagent inittrust approve <agent>quick "test"sync) on a fresh repo, confirm sync now reports "all N recent commit(s) are signed" instead of "1 invalid signature(s)"
  • Manual: simulate key rotation — change user.signingkey in the main repo, re-run any command that triggers configure_signing, confirm the new key gets added to allowed_signers without needing a manual edit

🤖 Generated with Claude Code

…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>
@dollspace-gay dollspace-gay self-assigned this May 12, 2026
@dollspace-gay dollspace-gay merged commit 66a1685 into develop May 12, 2026
6 checks passed
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.

trust approve / init does not register driver key in allowed_signers, causing driver-workspace commits to fail verification

1 participant