auth.md
markdown
| 1 | # `muse auth` — Identity Management Reference |
| 2 | |
| 3 | Muse has two primary user types: **humans** and **agents**. Both are |
| 4 | first-class identities, authenticated identically. This command manages the |
| 5 | full identity lifecycle: login, introspection, and logout. |
| 6 | |
| 7 | --- |
| 8 | |
| 9 | ## Table of Contents |
| 10 | |
| 11 | 1. [Why not `muse config set auth.token`?](#why-not-muse-config-set-authtoken) |
| 12 | 2. [Identity File — `~/.muse/identity.toml`](#identity-file) |
| 13 | 3. [Commands](#commands) |
| 14 | - [muse auth login](#muse-auth-login) |
| 15 | - [muse auth whoami](#muse-auth-whoami) |
| 16 | - [muse auth logout](#muse-auth-logout) |
| 17 | 4. [Authentication Flows](#authentication-flows) |
| 18 | 5. [Token Security Best Practices](#token-security-best-practices) |
| 19 | 6. [Environment Variables](#environment-variables) |
| 20 | |
| 21 | --- |
| 22 | |
| 23 | ## Why not `muse config set auth.token`? |
| 24 | |
| 25 | Credentials belong to the **machine**, not the repository. Storing a token |
| 26 | inside `.muse/config.toml` has three problems: |
| 27 | |
| 28 | 1. **Accidental commit** — `.muse/config.toml` is in the repo directory and |
| 29 | could be committed to version control, exposing the token to everyone with |
| 30 | access. |
| 31 | 2. **Scope creep** — one machine may work with many repositories; the token is |
| 32 | a machine-scoped credential. |
| 33 | 3. **Tight coupling** — tying credentials to a repo prevents sharing them |
| 34 | across projects on the same machine. |
| 35 | |
| 36 | Muse separates these concerns: |
| 37 | |
| 38 | | File | Stores | Scope | |
| 39 | |---|---|---| |
| 40 | | `.muse/config.toml` | Hub URL (`[hub] url`) | Per repository | |
| 41 | | `~/.muse/identity.toml` | Bearer token | Per machine | |
| 42 | |
| 43 | The CLI reads the hub URL from the repo, the token from the machine. The two |
| 44 | are combined only at request time — the token is never written to |
| 45 | `.muse/config.toml`. |
| 46 | |
| 47 | --- |
| 48 | |
| 49 | ## Identity File |
| 50 | |
| 51 | **Path:** `~/.muse/identity.toml` |
| 52 | **Permissions:** `0o600` (read/write owner only) |
| 53 | **Directory permissions:** `0o700` (owner only) |
| 54 | |
| 55 | ### File format |
| 56 | |
| 57 | TOML with one section per hub hostname. The section key is the bare hostname |
| 58 | (no scheme, no path), always lowercase: |
| 59 | |
| 60 | ```toml |
| 61 | ["musehub.ai"] |
| 62 | type = "human" |
| 63 | name = "Alice" |
| 64 | id = "usr_abc123" |
| 65 | token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." |
| 66 | |
| 67 | ["staging.musehub.ai"] |
| 68 | type = "agent" |
| 69 | name = "composer-v2" |
| 70 | id = "agt_def456" |
| 71 | token = "muse_tok_..." |
| 72 | capabilities = ["read:*", "write:midi", "commit"] |
| 73 | ``` |
| 74 | |
| 75 | ### `IdentityEntry` fields |
| 76 | |
| 77 | | Field | Type | Required | Description | |
| 78 | |---|---|---|---| |
| 79 | | `type` | `"human"` \| `"agent"` | Yes | Identity type | |
| 80 | | `token` | `str` | Yes | Bearer token — never logged | |
| 81 | | `name` | `str` | No | Display name (human name or agent handle) | |
| 82 | | `id` | `str` | No | Hub-assigned identity ID | |
| 83 | | `capabilities` | `list[str]` | No | Agent capability strings (empty for humans) | |
| 84 | |
| 85 | ### URL normalisation |
| 86 | |
| 87 | The file is keyed by bare hostname, not full URL. The following all resolve to |
| 88 | the same entry: |
| 89 | |
| 90 | ``` |
| 91 | https://musehub.ai |
| 92 | https://musehub.ai/repos/my-song |
| 93 | MUSEHUB.AI |
| 94 | musehub.ai |
| 95 | ``` |
| 96 | |
| 97 | Userinfo embedded in the URL (`user:password@musehub.ai`) is stripped before |
| 98 | use as the key — credentials are never stored inside the hostname key. |
| 99 | |
| 100 | ### Security properties |
| 101 | |
| 102 | - The file is written with `0o600` permissions **from byte zero** using |
| 103 | `os.open()` + `os.fchmod()`, eliminating the TOCTOU window that |
| 104 | `write_text()` + `chmod()` creates. |
| 105 | - Writes are atomic: data goes to a temp file, then `os.replace()` renames it |
| 106 | over the target. A kill signal during write leaves the old file intact. |
| 107 | - A symlink at the target path is refused — symlink-based credential-overwrite |
| 108 | attacks are blocked. |
| 109 | - An exclusive advisory lock (`fcntl.flock`) prevents concurrent write races |
| 110 | when parallel agents log in simultaneously. |
| 111 | - The file is never read or written as part of a repository snapshot. |
| 112 | |
| 113 | --- |
| 114 | |
| 115 | ## Commands |
| 116 | |
| 117 | ### muse auth login |
| 118 | |
| 119 | Store a bearer token in `~/.muse/identity.toml`. |
| 120 | |
| 121 | ``` |
| 122 | muse auth login [OPTIONS] |
| 123 | ``` |
| 124 | |
| 125 | **Options:** |
| 126 | |
| 127 | | Flag | Env var | Description | |
| 128 | |---|---|---| |
| 129 | | `--token TOKEN` | `MUSE_TOKEN` | Bearer token. Reads `MUSE_TOKEN` if not passed explicitly. | |
| 130 | | `--hub URL` | — | Hub URL. Falls back to `[hub] url` in `.muse/config.toml`. | |
| 131 | | `--name NAME` | — | Display name for this identity. | |
| 132 | | `--id ID` | — | Hub-assigned identity ID (stored for reference). | |
| 133 | | `--agent` | — | Mark this identity as an agent (default: human). | |
| 134 | |
| 135 | **Resolution order for the token:** |
| 136 | |
| 137 | 1. `MUSE_TOKEN` environment variable (preferred — does not appear in shell history) |
| 138 | 2. `--token` CLI flag (warns about shell history exposure) |
| 139 | 3. Interactive prompt via `getpass.getpass` (human-only flow) |
| 140 | |
| 141 | **Examples:** |
| 142 | |
| 143 | ```bash |
| 144 | # Human — interactive prompt |
| 145 | muse auth login --hub https://musehub.ai |
| 146 | |
| 147 | # Agent — non-interactive, token from environment variable |
| 148 | MUSE_TOKEN=$MY_SECRET muse auth login \ |
| 149 | --hub https://musehub.ai \ |
| 150 | --agent \ |
| 151 | --name "composer-v2" \ |
| 152 | --id "agt_xyz" |
| 153 | |
| 154 | # Human — hub URL from repo config (no --hub needed after muse hub connect) |
| 155 | muse auth login |
| 156 | |
| 157 | # Override identity metadata after initial login |
| 158 | muse auth login --token $MUSE_TOKEN --name "Alice (updated)" --hub musehub.ai |
| 159 | ``` |
| 160 | |
| 161 | **What login does:** |
| 162 | |
| 163 | 1. Resolves the hub URL from `--hub` or the repo's `[hub] url` config. |
| 164 | 2. Resolves the token from the environment, the flag, or an interactive prompt. |
| 165 | 3. Warns if the token was passed via the `--token` CLI flag (shell history risk). |
| 166 | 4. Creates or updates the `[<hostname>]` section in `~/.muse/identity.toml`. |
| 167 | 5. Sets directory and file permissions (`0o700` / `0o600`). |
| 168 | |
| 169 | --- |
| 170 | |
| 171 | ### muse auth whoami |
| 172 | |
| 173 | Display the stored identity for a hub. |
| 174 | |
| 175 | ``` |
| 176 | muse auth whoami [OPTIONS] |
| 177 | ``` |
| 178 | |
| 179 | **Options:** |
| 180 | |
| 181 | | Flag | Description | |
| 182 | |---|---| |
| 183 | | `--hub URL` | Hub URL to inspect. Defaults to the repo's configured hub. | |
| 184 | | `--all` | Show identities for all configured hubs. | |
| 185 | | `--json` | Emit JSON instead of human-readable output. | |
| 186 | |
| 187 | The raw token is **never** shown. The output indicates only whether a token is |
| 188 | set (`set (Bearer ***)` or `not set`). |
| 189 | |
| 190 | **Examples:** |
| 191 | |
| 192 | ```bash |
| 193 | # Human-readable output for the current repo's hub |
| 194 | muse auth whoami |
| 195 | |
| 196 | # JSON output — for agent scripts |
| 197 | muse auth whoami --json |
| 198 | |
| 199 | # Inspect a specific hub |
| 200 | muse auth whoami --hub https://staging.musehub.ai |
| 201 | |
| 202 | # Show all stored identities |
| 203 | muse auth whoami --all |
| 204 | ``` |
| 205 | |
| 206 | **JSON output shape:** |
| 207 | |
| 208 | ```json |
| 209 | { |
| 210 | "hub": "musehub.ai", |
| 211 | "type": "human", |
| 212 | "name": "Alice", |
| 213 | "id": "usr_abc123", |
| 214 | "token_set": "true", |
| 215 | "capabilities": [] |
| 216 | } |
| 217 | ``` |
| 218 | |
| 219 | **Exit codes:** |
| 220 | |
| 221 | - `0` — identity found and displayed. |
| 222 | - Non-zero — no identity stored for the specified hub. Useful in agent scripts: |
| 223 | |
| 224 | ```bash |
| 225 | muse auth whoami --hub musehub.ai --json || muse auth login --agent --hub musehub.ai |
| 226 | ``` |
| 227 | |
| 228 | --- |
| 229 | |
| 230 | ### muse auth logout |
| 231 | |
| 232 | Remove stored credentials for a hub. |
| 233 | |
| 234 | ``` |
| 235 | muse auth logout [OPTIONS] |
| 236 | ``` |
| 237 | |
| 238 | **Options:** |
| 239 | |
| 240 | | Flag | Description | |
| 241 | |---|---| |
| 242 | | `--hub URL` | Hub URL to log out from. Defaults to the repo's configured hub. | |
| 243 | | `--all` | Remove credentials for ALL configured hubs. | |
| 244 | |
| 245 | The bearer token is deleted from `~/.muse/identity.toml`. The hub URL in |
| 246 | `.muse/config.toml` is preserved — use `muse hub disconnect` to remove the hub |
| 247 | association from the repository as well. |
| 248 | |
| 249 | **Examples:** |
| 250 | |
| 251 | ```bash |
| 252 | # Log out from the current repo's hub |
| 253 | muse auth logout |
| 254 | |
| 255 | # Log out from a specific hub |
| 256 | muse auth logout --hub https://staging.musehub.ai |
| 257 | |
| 258 | # Remove all stored credentials |
| 259 | muse auth logout --all |
| 260 | ``` |
| 261 | |
| 262 | --- |
| 263 | |
| 264 | ## Authentication Flows |
| 265 | |
| 266 | ### Human flow (interactive) |
| 267 | |
| 268 | ```bash |
| 269 | # 1. Connect the repo to a hub |
| 270 | muse hub connect https://musehub.ai |
| 271 | |
| 272 | # 2. Log in (prompts for token) |
| 273 | muse auth login |
| 274 | |
| 275 | # 3. Push |
| 276 | muse push |
| 277 | ``` |
| 278 | |
| 279 | ### Agent flow (non-interactive) |
| 280 | |
| 281 | ```bash |
| 282 | # In a CI/CD pipeline or autonomous agent: |
| 283 | MUSE_TOKEN="$SECRET_FROM_VAULT" muse auth login \ |
| 284 | --hub https://musehub.ai \ |
| 285 | --agent \ |
| 286 | --name "pipeline-agent-$BUILD_ID" |
| 287 | |
| 288 | # Now push without further prompts |
| 289 | muse push |
| 290 | ``` |
| 291 | |
| 292 | ### Checking authentication status in a script |
| 293 | |
| 294 | ```bash |
| 295 | if muse auth whoami --json > /dev/null 2>&1; then |
| 296 | echo "Authenticated — proceeding with push" |
| 297 | muse push |
| 298 | else |
| 299 | echo "Not authenticated — logging in" |
| 300 | MUSE_TOKEN="$SECRET" muse auth login --hub https://musehub.ai --agent |
| 301 | fi |
| 302 | ``` |
| 303 | |
| 304 | --- |
| 305 | |
| 306 | ## Token Security Best Practices |
| 307 | |
| 308 | **Prefer `MUSE_TOKEN` over `--token`.** |
| 309 | Tokens passed as `--token` appear in: |
| 310 | - Shell history (`~/.zsh_history`, `~/.bash_history`) |
| 311 | - Process listings (`ps aux` on Linux) |
| 312 | - `/proc/PID/cmdline` on Linux |
| 313 | |
| 314 | `MUSE_TOKEN` does not appear in any of these. Muse warns when a token is |
| 315 | passed via the CLI flag: |
| 316 | |
| 317 | ``` |
| 318 | ⚠️ Token passed via --token flag. |
| 319 | It may appear in your shell history and process listings. |
| 320 | For automation, prefer: MUSE_TOKEN=<token> muse auth login ... |
| 321 | ``` |
| 322 | |
| 323 | **Scope tokens to the minimum required capabilities.** |
| 324 | For read-only agents, request read-only tokens. For write agents, request |
| 325 | only the specific namespaces they need (e.g. `write:midi`). |
| 326 | |
| 327 | **Rotate tokens on schedule.** |
| 328 | Re-run `muse auth login` with a new token to overwrite the stored entry. The |
| 329 | old token is replaced atomically. |
| 330 | |
| 331 | **Do not share tokens across machines.** |
| 332 | Each machine should have its own token. This allows revoking access to a |
| 333 | single machine without affecting others. |
| 334 | |
| 335 | --- |
| 336 | |
| 337 | ## Environment Variables |
| 338 | |
| 339 | | Variable | Description | |
| 340 | |---|---| |
| 341 | | `MUSE_TOKEN` | Bearer token for `muse auth login`. Preferred over `--token`. | |
| 342 | | `MUSE_REPO_ROOT` | Override the repository root (used in tests and CI). | |
| 343 | |
| 344 | --- |
| 345 | |
| 346 | *See also:* |
| 347 | |
| 348 | - [`docs/reference/hub.md`](hub.md) — `muse hub connect/status/disconnect/ping` |
| 349 | - [`docs/reference/remotes.md`](remotes.md) — `muse push`, `muse fetch`, `muse clone` |
| 350 | - [`docs/reference/security.md`](security.md) — security architecture and identity store guarantees |