Files
cyclone/docs/superpowers/specs/2026-06-23-cyclone-auth-posture-alignment-design.md
T
Tyler 00f11f84b6 feat(sp24): align CLAUDE.md + REQUIREMENTS.md + ARCHITECTURE.md with auth work + AUTH_DISABLED WARNING
Replaces every stale 'no auth' / 'no authentication' / 'no second party to
authenticate' claim in the three top-level docs with the v1 posture: the
auth boundary is HTTP (bcrypt + HttpOnly session cookie; first admin
bootstrapped from CYCLONE_ADMIN_USERNAME + CYCLONE_ADMIN_PASSWORD env
vars); the file-system posture (SQLCipher at rest, macOS Keychain) is
unchanged; the threat model is still a stolen/imaged drive.

Closes requirements §11 R-1 (was Open; now Closed by SP24 — auth shipped
via the origin/main merge on 2026-06-23, SP24 reconciled the docs).

Also:
- backend/src/cyclone/__main__.py — emit WARNING when AUTH_DISABLED is
  True at boot so a misconfigured production deploy fails loudly
- §6.1 migrations table in ARCHITECTURE.md — list 0013 (auth_users_and_
  sessions) and 0014 (audit_log_user_id) with the renumbering note
- §4.2 module map — add the cyclone.auth.* package
- Three skills addenda: cyclone-tests (AUTH_DISABLED conftest bypass is
  mandatory context), cyclone-api-router (every router needs
  Depends(matrix_gate)), cyclone-spec (spec template threat-model is now
  auth-aware)

This also tracks CLAUDE.md in git for the first time (was previously
untracked; the SP24 doc updates are in scope for the increment).

Spec: docs/superpowers/specs/2026-06-23-cyclone-auth-posture-alignment-design.md
Plan: docs/superpowers/plans/2026-06-23-cyclone-auth-posture-alignment.md
2026-06-23 14:49:31 -06:00

89 lines
10 KiB
Markdown

# Cyclone Auth Posture Alignment — Design
**Date:** 2026-06-23
**Status:** Approved (in implementation on `sp24-doc-posture-alignment`, 2026-06-23)
**Branch:** `sp24-doc-posture-alignment` (off `css-reduction``docs/REQUIREMENTS.md` and `docs/ARCHITECTURE.md` were added on `css-reduction` and do not yet exist on `main`)
**Depends on:** The auth work that landed in main via the merge of `origin/main` on 2026-06-23 (commits `a25504b`..`39ae988`, 14 commits, the `feat(auth):`/`fix(auth):`/etc. series on `auth-rebased`).
**Replaces:** The "no auth, no LAN-bind, single operator" claim in `CLAUDE.md`, `docs/REQUIREMENTS.md`, and `docs/ARCHITECTURE.md` — the codebase no longer matches the doc and the divergence needs to be resolved one way or the other.
---
## 1. Overview
The auth work that landed in main is materially more than a stub. The codebase now has:
- A `users` and `sessions` SQLAlchemy model (migrations 0013 + 0014)
- A `User.password_hash` field using bcrypt
- An `auth_router` exposing `/api/auth/login`, `/api/auth/logout`, `/api/auth/me`
- A `matrix_gate` FastAPI dependency and a permissions matrix that gates every existing endpoint by role (admin / user / viewer)
- An `AUTH_DISABLED` flag (module-level in `cyclone.auth.deps`, also settable via `CYCLONE_AUTH_DISABLED=1` at boot) that the conftest autouse fixture flips to `True` so the test suite can run without a login round-trip per request
- A `RateLimitMiddleware` (SP19) that the conftest bypasses via `app.state.testing`
- A `RequireAuth` route guard and an `AuthProvider` React context on the frontend
- A sidebar user indicator + logout button on the shell
- A `cyclone admin` CLI for creating users and rotating passwords
But `CLAUDE.md`, `docs/REQUIREMENTS.md`, and `docs/ARCHITECTURE.md` (all three added or updated on the `css-reduction` branch in commit `12311bf` / `a39bfb2`) still say:
- `CLAUDE.md:7` — "Local-only by design: binds to `127.0.0.1`, **no auth**, no internet exposure."
- `CLAUDE.md:182` — "**Local-only by design.** The backend binds to `127.0.0.1`, has no auth, and the threat model is a stolen/imaged drive, not a remote attacker. **Don't add internet exposure or auth without an explicit spec.**"
- `docs/REQUIREMENTS.md:36` — "**Local-only on purpose:** binds `127.0.0.1`, **no auth**, no internet exposure, single operator, single machine, one trading partner"
- `docs/REQUIREMENTS.md:39` — "**Threat model:** a process on the same host. No second party to authenticate; no second host to harden against."
- `docs/REQUIREMENTS.md:154` — NFR-1: "no auth, no API key"
- `docs/ARCHITECTURE.md:16` — "has no authentication because the threat model is a stolen or imaged drive, not a remote attacker"
- `docs/ARCHITECTURE.md:703` — "Multi-user / multi-host — **no auth**, no LAN-bind, no Docker, no reverse proxy. The operator is one person, the host is one machine. (SP23 fork if this changes.)"
The pre-auth claims in the docs are now false. The threat model is no longer "a process on the same host" — there is at minimum a password boundary that requires a deliberate authentication step before any data is returned.
## 2. Goals
1. **Decide the canonical auth posture.** Is this v1 with a single local operator who has to log in, or is it the first step toward SP23 (Ubuntu + Docker + RBAC + LAN-bind)? Pick one and write it down; the docs and the code must agree.
2. **Update the three stale top-level docs** (`CLAUDE.md`, `docs/REQUIREMENTS.md`, `docs/ARCHITECTURE.md`) to reflect the chosen posture. Every "no auth" claim is replaced with the truth; every "single operator, single host" claim is qualified by the new auth boundary.
3. **Update the relevant Cyclone superpowers skills** that still describe the pre-auth system: at minimum `cyclone-tests` (conftest's `AUTH_DISABLED` flag is now mandatory context for any new test file), `cyclone-api-router` (the `matrix_gate` dependency is now part of every router), and `cyclone-spec` (the SP-N spec template's "threat model" section needs an auth-aware example).
4. **Make `AUTH_DISABLED` discoverable in the conftest.** The conftest autouse fixture flips `cyclone.auth.deps.AUTH_DISABLED = True` for every test, and the test suite relies on this. A new test file that imports `cyclone.auth.deps` and reads the flag directly will see it as `True` and may make wrong assumptions. A `cyclone.auth.testing` helper that documents "this is what the test suite does to the auth flag, and how to opt out per-test" would make the dependency explicit.
5. **Tighten the migration sequence.** Migrations 0013 and 0014 (auth_users_and_sessions, audit_log_user_id) are auth-related. They currently have no docstring beyond the filename. The migrations table in `docs/ARCHITECTURE.md` should list them and explain the dependency (the `audit_log.user_id` FK in 0014 is a forward reference to `users.id` from 0013, so the renumbering on the `claims-unique-fix` branch — 0013/0014 → 0015/0016 — was necessary to keep the auth migrations at the front of the chain).
## 3. Non-goals
- **Reverting the auth work.** It ships in main, it's tested, and the operator can no longer use Cyclone without logging in. Going back is a separate decision the user would have to ask for explicitly; this SP assumes the auth work stays.
- **Shipping SP23 (Ubuntu + Docker + LAN-bind).** SP23 is its own design (`docs/superpowers/specs/2026-06-22-cyclone-ubuntu-docker-deployment-design.md`) and is still "awaiting user decision." This SP only reconciles the doc/code divergence that the auth-merge created; it does not advance the LAN-bind / Docker conversation.
- **Adding new auth features.** No SSO, no MFA, no per-claim ACL, no audit log viewer UI. The auth in main is what's documented; this SP just describes it.
- **Changing the data model.** The `users` / `sessions` / `audit_log.user_id` schema stays. The bcrypt-hashed password and HttpOnly session cookie stay. The `admin` / `user` / `viewer` role enum stays.
## 4. Decisions to make (questions for the user)
These are the points that the spec can't answer on its own. They each have a default that matches the auth work in main, but the user may want to override.
1. **What is the v1 posture?** The auth work in main binds to `127.0.0.1` (still) and gates every endpoint behind `matrix_gate` (now). The default reading is "single-operator local-only, but with a login screen." Alternatives: revert the auth (separate decision), or treat this as a stepping stone to SP23.
- **Default:** v1 = single-operator local-only with auth. SP23 is a future decision.
2. **How is the first admin created?** `cyclone.auth.bootstrap` reads `CYCLONE_ADMIN_USERNAME` + `CYCLONE_ADMIN_PASSWORD` from the env and creates the row on first boot (no users exist) — see `backend/src/cyclone/auth/bootstrap.py:45-65`. If the env vars are unset and no users exist, `cyclone serve` errors out with a message telling the operator to set them. The `cyclone admin create-user` CLI is a follow-up convenience for adding more users after the first one.
- **Default:** the env-var bootstrap stays. Document the env vars in `CLAUDE.md` under the existing `Install` / `Dev` sections.
3. **Does the `AUTH_DISABLED` escape hatch ship as a real production knob?** It's a `cyclone.auth.deps.AUTH_DISABLED` module-level boolean that the conftest flips to `True` for the test suite, AND `CYCLONE_AUTH_DISABLED=1` sets it at boot via `cyclone.auth.bootstrap:37-43`. The current behavior is "synthetic admin, no role check" when the flag is set.
- **Default:** keep the env-var escape hatch (it makes local dev + tests possible), but add a startup log line in `cyclone serve` that screams `WARNING: CYCLONE_AUTH_DISABLED=1 — all requests treated as admin, dev only` so a misconfigured production deploy fails loudly. Make the conftest path explicit in `cyclone-tests`.
4. **Should the docs be explicit about what the auth boundary is and isn't?** The default reading is "the auth boundary is the HTTP layer; the file-system and the SQLite file are still bound by the local-only threat model (stolen/imaged drive, SQLCipher at rest, etc.)."
- **Default:** the auth boundary is HTTP-layer only. SQLCipher, Keychain, and the 127.0.0.1 bind are unchanged.
## 5. Plan
The plan lives in `docs/superpowers/plans/2026-06-23-cyclone-auth-posture-alignment.md` once the spec is approved. The shape of the plan is:
1. `CLAUDE.md` — replace lines 7 and 182 with the v1 posture ("local-only with auth; auth boundary is HTTP; default bootstrap on first run"). Update the `Frontend at a glance` / `Backend at a glance` sections to mention `cyclone.auth` and `matrix_gate`. Note the `AUTH_DISABLED` test flag in `cyclone-tests`.
2. `docs/REQUIREMENTS.md` — replace the "Local-only on purpose" line 36 and the "Threat model" line 39 with the v1 posture. Update NFR-1 (line 154) to reflect that auth IS now part of the contract. Update §6 (FRs) to add an "Auth boundary" subsection. Update §11 (Roadmap) to note that SP24 lands the auth posture and SP23 is a separate future decision.
3. `docs/ARCHITECTURE.md` — replace line 16 and line 703. Add an "Auth" section to the module map (`cyclone.auth` package, the `matrix_gate` dependency, the conftest bypass). Update the migrations table to list 0013 and 0014 and explain why the renumbering on the SP22 branch was necessary.
4. The three Cyclone superpowers skills under `.superpowers/skills/` that describe tests, routing, and spec shape: each gets a one-paragraph addendum.
5. `cyclone serve` startup log: emit a `WARNING: AUTH_DISABLED` line if `cyclone.auth.deps.AUTH_DISABLED` is `True` at boot. The flag is off by default; only the conftest flips it.
## 6. Verification
- The three top-level docs no longer contain "no auth" / "no authentication" / "no second party to authenticate" claims that are false.
- The Cyclone skills addenda are present and self-consistent.
- The startup warning fires when `AUTH_DISABLED = True` and is silent otherwise.
- The existing test suite still passes (the auth changes are in main; this SP only touches docs + the startup warning).
## 7. Out of scope
- Any new feature beyond docs + skills addenda + startup warning.
- Any change to the `users` / `sessions` / `audit_log` schema.
- Any change to the role matrix or the per-endpoint gate.
- Any change to SP23 (separate spec, separate decision).
- The CLAUDE.md contradiction is the canary; the rest of the doc set may have other places where the pre-auth claim leaked in. The implementation step sweeps `docs/` and `*.md` files under `.superpowers/skills/` for the strings "no auth", "no authentication", "single operator, single host" and either fixes them or explicitly marks them as historical context.