Skip to content

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

@dollspace-gay

Description

@dollspace-gay

Summary

In a driver workspace (agent.json role: "driver"), 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 workspace's user.signingkey — is never added to allowed_signers. As a result, every hub commit produced by that workspace is signed by the driver's key but fails git verify-commit because the verifier doesn't trust that key.

The result: crosslink sync reports Signing enforcement FAILED: 0 unsigned, N invalid signature(s) in last N commit(s) after the very first trust approve, even though everything else is set up correctly.

Reproduction

  1. crosslink init in a fresh repo (driver role).
  2. crosslink agent init → publishes the agent key.
  3. crosslink trust approve <agent> → "Bootstrap complete — signing enforcement is now active."
  4. Make any hub-mutating call: crosslink quick "test".
  5. crosslink sync → fails enforcement: 1 invalid signature(s) in last 1 commit(s).

git log crosslink/hub --format='%G? %GK' shows U SHA256:<driver-fingerprint> for the new commit — the signature itself is good (sshsig-valid), but the key isn't in allowed_signers, so git reports it unverified.

Root cause (file:line)

  • commands/trust.rs:42-120 (approve): reads trust/keys/<agent-id>.pub, adds only the agent's principal to allowed_signers. Records the driver's fingerprint as approved_by metadata but does not register it as a trusted signer.
  • sync/trust.rs:92-174 (configure_signing): correctly sets user.signingkey to the driver's key for driver workspaces (lines 133-148), and points gpg.ssh.allowedSignersFile at the cache copy. But signing::configure_git_ssh_signing (signing.rs:372-403) only writes git config — it does not append the driver key to the allowed_signers file.
  • sync/cache.rs:163 (init_cache): does not write the driver key into trust/allowed_signers either.

So the driver key has no path into allowed_signers short of manual editing.

Manual workaround (verified)

# Inside the affected workspace
KEY=$(cat .crosslink/driver-key.pub)
echo "doll@host $KEY" >> .crosslink/.hub-cache/trust/allowed_signers
( cd .crosslink/.hub-cache && git add trust/allowed_signers && \
  git commit -m "trust: register driver key in allowed_signers (manual workaround)" )
crosslink sync   # reports "all N recent commit(s) are signed"

The first commit is self-bootstrapping: by the time git verify-commit reads allowed_signers, the file already contains the driver key, so the commit validates against itself.

Suggested fix

Two reasonable options:

A. Register the driver key alongside the agent on first trust approve.
In commands/trust.rs::approve, after the agent entry is added, also append the driver's pubkey (resolved from .crosslink/driver-key.pub or git config user.signingkey) with a sensible principal (driver:<email> or <USER>@<host>).

B. Have configure_signing (or init_cache / agent init) ensure the active signing key is in allowed_signers.
Whenever configure_signing selects a key (driver or agent), check allowed_signers and append the corresponding pubkey if missing. This makes the invariant "the workspace's signer is always trusted" hold automatically.

Either path closes the gap. (B) is more robust because it covers driver-key rotation and re-init flows; (A) is the minimal targeted change.

Severity

High for any user setting up a fresh driver workspace, because:

  • The error message (Signing enforcement FAILED) is alarming but actionable guidance points at re-running setup, which doesn't fix it.
  • The natural "fix" (crosslink integrity sign-backfill) reports "no unsigned entries — nothing to do" because the commits ARE signed, just not trusted.
  • Workarounds require editing the hub cache by hand, which most users won't attempt.

Environment

  • crosslink workspace at /home/doll/proto-blue (Rust atproto SDK)
  • Triggered after migrating to a fresh WSL instance with new SSH keys (i.e. a re-bootstrap scenario)
  • Reproduced with current crosslink CLI; source inspected at /home/doll/crosslink commit (current HEAD)

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions