gabriel / musehub public
Open #72 security
filed by gabriel human · 7 days ago

Local identity DAG mirror — clone and sync ~/.muse/identity/ on every key event

0 Anchors
Blast radius
Churn 30d
0 Proposals

Overview

The identity DAG repo is currently hub-only — it was born on MuseHub and has never been cloned locally. ~/.muse/identity.toml stores the current key fingerprint and HD derivation path, but carries no VCS history. If the hub is wiped or compromised, the full identity DAG is gone with no recovery path.

Mists are intentionally hub-first (shareable artifacts, no offline iteration needed). Identity is different: it tracks key transitions, trust anchors, and provenance over time. That history is security-critical and must survive hub loss. The fix is a local clone under ~/.muse/identity/ that is automatically kept in sync whenever the identity changes.


Phase 1 — Audit the current identity repo structure on the hub

Before designing anything, understand what the identity DAG actually contains:

# What branches exist?
muse -C ~/ecosystem/musehub hub repo list --json \
  | python3 -c "import sys,json; [print(r['slug'], r.get('repoId','')) for r in json.load(sys.stdin)['repos'] if r['slug']=='identity']"

# Clone it and inspect
muse clone https://staging.musehub.ai/gabriel/identity ~/.muse/identity-inspect
muse -C ~/.muse/identity-inspect log --json | python3 -c "
import sys,json
d=json.load(sys.stdin)
for c in d['commits']:
    print(c['commit_id'][:16], c['message'][:80])
"
muse -C ~/.muse/identity-inspect read --json --manifest

Answer these questions:

  • What commits exist? What do they record (key rotations, trust grants, agent delegations)?
  • What is the domain of the repo — code, mist, or a dedicated identity domain?
  • What is the schema of the files tracked in the repo (JSON, TOML, custom)?
  • Is there a single main branch, or a DAG with multiple heads?

Produce a written audit. Do not write any code in this phase.


Phase 2 — Define the local sync contract

The local identity clone lives at ~/.muse/identity/. It must:

  1. Exist on first keygenmuse auth keygen should create the hub identity repo AND clone it locally in a single atomic operation. After keygen, ~/.muse/identity/ is present and up to date.

  2. Stay current on every identity-mutating operation — any command that writes to the identity DAG on the hub (muse auth rotate, muse auth logout, future muse auth trust, muse agent register) must pull the new commit into ~/.muse/identity/ before returning.

  3. Be read-only from the local side — the local clone is a pull-only mirror. Identity mutations always go hub-first (sign → push → pull-to-local). Never push directly from ~/.muse/identity/ to avoid divergence.

  4. Survive hub unavailability gracefully — if the hub is unreachable during a pull-to-local, log a warning but do not fail the mutation. The next successful hub contact syncs the gap. ~/.muse/identity.toml remains the authoritative source for the current key fingerprint regardless of sync state.

Document these rules as a spec before implementation.


Phase 3 — muse auth keygen — clone identity repo after hub registration

After keygen registers the new key with the hub and the hub creates the identity repo:

# pseudocode — adapt to actual auth keygen implementation
identity_local = Path.home() / ".muse" / "identity"
if not identity_local.exists():
    clone_repo(
        url=f"{hub_url}/{handle}/identity",
        dest=identity_local,
        private_key=derived_key,
    )
    logger.info("✅ Identity repo cloned to %s", identity_local)
else:
    pull_repo(identity_local, remote="origin", branch="main")
    logger.info("✅ Identity repo synced at %s", identity_local)

The clone must use the MSign-authenticated transport (same as muse clone for private repos). It must not prompt for credentials — the derived Ed25519 key from identity.toml is used automatically.


Phase 4 — Pull-to-local after every identity mutation

Wrap each identity-mutating CLI command (auth rotate, auth logout, future auth trust) with a post-mutation sync:

async def _sync_identity_local(hub_url: str, handle: str) -> None:
    """Pull latest identity DAG commit to ~/.muse/identity/."""
    identity_local = Path.home() / ".muse" / "identity"
    try:
        if identity_local.exists():
            await pull(identity_local, remote="origin", branch="main")
        else:
            await clone(f"{hub_url}/{handle}/identity", identity_local)
        logger.info("✅ Identity repo synced")
    except Exception as exc:
        logger.warning("⚠️ Identity sync failed (hub unreachable?): %s", exc)
        # non-fatal — identity.toml remains authoritative

Call this after every successful hub write, before returning to the user.


Phase 5 — muse auth status — show sync health

Add sync state to muse auth whoami output:

{
  "handle": "gabriel",
  "hub": "https://staging.musehub.ai",
  "fingerprint": "sha256:220897...",
  "identity_local": "~/.muse/identity",
  "identity_synced": true,
  "identity_local_tip": "sha256:abcd1234...",
  "identity_hub_tip":   "sha256:abcd1234...",
  "identity_behind": 0
}

If identity_behind > 0, print a warning:

⚠️  Local identity is 2 commits behind hub. Run: muse auth sync

Add muse auth sync as an explicit pull command for manual recovery:

muse auth sync --hub https://staging.musehub.ai

Phase 6 — Backfill existing identities

For users who already have ~/.muse/identity.toml but no local ~/.muse/identity/ clone (the current state for all existing users), provide a one-shot backfill:

muse auth sync --hub https://staging.musehub.ai
# ✅ Identity repo cloned to /Users/gabriel/.muse/identity (12 commits)

This should also run automatically on the first invocation of any muse auth command after this change is deployed — detect missing local clone, clone silently, continue. No user action required beyond upgrading muse.


Acceptance criteria

[ ] muse auth keygen clones identity repo to ~/.muse/identity/ after hub registration
[ ] muse auth rotate pulls new commit to ~/.muse/identity/ after key rotation
[ ] muse auth logout tombstones local clone (or pulls the logout commit)
[ ] Hub unavailability during sync is a warning, not an error — mutation still succeeds
[ ] muse auth whoami --json reports identity_synced, identity_behind
[ ] muse auth sync --hub <url> backfills missing local clones
[ ] First invocation of any muse auth command auto-backfills if local clone is missing
[ ] Local clone is pull-only — no push path from ~/.muse/identity/ exists
[ ] ~/.muse/identity/ uses MSign auth transport — no credential prompts
[ ] Existing users on upgrade get silent auto-backfill on first auth command
Activity
gabriel opened this issue 7 days ago
No activity yet. Use the CLI to comment.