gabriel / muse public
issue-99-agent-keygen-register-key-mismatch.md markdown
111 lines 5.2 KB
Raw
sha256:0a40077c8b2e56d9f8eccdf508a2c59ffc8b635bad6d4939ad8adc98804f8ace docs: add local source-of-truth for musehub#99 (agent keyge… Sonnet 5 19 hours ago

muse auth register --agent-id registers a different key than muse auth keygen generated

Severity

High / security-relevant. The fingerprint a user is shown and told to trust at keygen time is not the key that ends up registered with the hub for that agent. Anyone auditing an agent's identity by its keygen-time fingerprint would be checking the wrong key entirely.

Background

Discovered while reviewing the "When an agent needs its own key" section of the Build with Muse episode 04 script — the printed HD path is identical across keygen and register output, but the reported key fingerprint differs between the two commands for what's supposed to be the same key.

Repro (reproduced twice, clean scratch repos, zero passphrase interaction)

muse init
muse auth keygen --agent-id ci-bot-demo --hub https://staging.musehub.ai < /dev/null
#    Fingerprint (SHA-256): sha256:71d461bf302a907a40e7977b9a08fdc1b0c5ab86a45c0a8f7ce1cf072b3ccab2
#    HD path: m/1075233755'/1660078172'/1'/2106982183'/0'/1449979433'/0'  (slot 2106982183)

muse auth register --hub https://staging.musehub.ai --agent-id ci-bot-demo \
  --handle ci-bot-demo-test --provisioned-by gabriel < /dev/null
#    Identity ID: sha256:04585f7c6f178ce9159f976de3d5a2cc5d60cb145d7e8e16b3d1f214154bf783
#    Auth method: ed25519 (key fingerprint: sha256:04585f7c6…)
#    HD path: m/1075233755'/1660078172'/1'/2106982183'/0'/1449979433'/0'

Identical printed HD path. Completely different fingerprint. Reproduced a second time with different random values, same divergence pattern.

Root cause

muse/cli/commands/auth.py:

keygen's agent-key path (~line 1078) derives the real key via a custom KDF, not a literal HD path walk:

operator_seed = mnemonic_to_seed(operator_mnemonic, passphrase)
slot = agent_id_to_slot(agent_id)
agent_sub_seed = derive_agent_sub_seed(operator_seed, DOMAIN_IDENTITY, slot)
pub_b64, fingerprint = derive_hd_public_info(agent_sub_seed, hub=hub_idx)

hd_path_str = muse_path(DOMAIN_IDENTITY, ENTITY_AGENT, slot, hub=hub_idx)

hd_path_str here is a cosmetic label — built from muse_path(...) purely for display/storage — describing the derivation in human-readable form. It was never meant to be independently re-walked as a literal SLIP-10 path; the actual key material comes from derive_agent_sub_seed() + derive_hd_public_info().

register's path (~line 1910), when re-deriving the key from a stored identity entry to sign the challenge nonce, does the opposite: it takes that stored hd_path string and treats it as a literal path to walk:

seed = mnemonic_to_seed(mnemonic)
dk = derive_path(seed, hd_path_for_register)
private_key = to_ed25519_private_key(dk)

derive_path() (plain SLIP-10 path derivation) and derive_agent_sub_seed() -> derive_hd_public_info() (custom KDF) are different algorithms. Feeding the same nominal path string into both produces two different keys from the same mnemonic. register ends up generating and registering a key that was never shown to the user, was never validated against Fingerprint (SHA-256) at keygen time, and has no relationship to the identity the user believed they were provisioning.

Impact

  • Any provenance/trust claim based on the keygen-time fingerprint is false for agent keys — the registered key is different.
  • muse auth show / any code path that re-derives from the stored hd_path string via derive_path() for an agent entry is affected the same way. (Human entries may or may not hit this same divergence — needs checking; the human keygen path was not exercised in this repro.)
  • Downstream: any commit signed by the mis-derived key still verifies fine cryptographically (self-consistent), but the identity chain from "the key gabriel was shown" to "the key musehub has on file" is broken.

Scope

  1. Confirm whether the human (non-agent) keygen/register/auth show round-trip has the same bug, or whether it's agent-specific (the human path may use derive_path() consistently everywhere — verify).
  2. Pick one canonical derivation for agent keys and make every code path that re-derives from a stored identity entry use it consistently: either (a) make register/auth show/anywhere else that re-derives agent keys call the same derive_agent_sub_seed + hub-scoped derivation keygen uses, or (b) make keygen actually derive via derive_path(seed, hd_path_str) so the printed path is literally accurate and re-walkable.
  3. Add a regression test that keygens an agent key, registers it, and asserts the fingerprint printed/returned by both commands is identical — this exact bug would have been caught immediately by such a test.
  4. Audit for any other command that re-derives a key from a stored hd_path string via derive_path() for an agent identity — every one of those call sites has the same latent bug.

Acceptance criteria

  • muse auth keygen --agent-id X followed by muse auth register --agent-id X reports the same fingerprint at both steps, verified by a new automated test.
  • Existing agent identities already registered under the mismatched derivation are audited — decide whether they need re-provisioning.
File History 1 commit
sha256:0a40077c8b2e56d9f8eccdf508a2c59ffc8b635bad6d4939ad8adc98804f8ace docs: add local source-of-truth for musehub#99 (agent keyge… Sonnet 5 19 hours ago