# `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) ```bash 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: ```python 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: ```python 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.