Files
cyclone/docs/superpowers/specs/2026-06-23-cyclone-auth-posture-alignment-design.md
T
Tyler 9e595503b7 docs(spec): SP24 — align docs and decisions with the auth work in main
The auth-merge that landed on 2026-06-23 (14 commits, a25504b..39ae988)
adds real auth: User/Session models, bcrypt, matrix_gate, AUTH_DISABLED,
a login router, RBAC on every existing endpoint, and a RequireAuth
guard on the React shell. But CLAUDE.md, docs/REQUIREMENTS.md, and
docs/ARCHITECTURE.md (all added/updated on the css-reduction branch)
still describe the pre-auth "no auth, local-only, single operator,
single host" posture. The doc/code divergence is real and would
confuse anyone reading the docs.

This is the spec for SP24 — the alignment. It is intentionally a
docs/skills-only SP: no schema, no role, no gate changes. The plan
section enumerates the doc lines to update; the implementation step
sweeps the markdown for residual "no auth" claims.

Draft, awaiting user sign-off per the SP-N spec flow.
2026-06-23 14:49:21 -06:00

10 KiB

Cyclone Auth Posture Alignment — Design

Date: 2026-06-23 Status: Draft, awaiting user sign-off Branch: main (auth code already landed; this SP aligns docs + decisions) 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.