Skip to content

fix(security): auth-status reporting, stale docs, error surfacing#80

Merged
steve-krisjanovs merged 1 commit into
mainfrom
fix/sweep-auth-status-and-docs
Jul 1, 2026
Merged

fix(security): auth-status reporting, stale docs, error surfacing#80
steve-krisjanovs merged 1 commit into
mainfrom
fix/sweep-auth-status-and-docs

Conversation

@steve-krisjanovs

Copy link
Copy Markdown
Contributor

Summary

Multi-pass sweep after the three auth security fixes (#77, #78, #79) landed — a live web-UI walkthrough (my own) and a parallel CLI audit (a subagent) converged independently on the same root bug.

Real bug: the startup banner, page footer, and /health's authEnabled field all checked only legacy v1 tokens (authStore.authEnabled()). A fresh v2-only install — the current default, since v1 tokens can no longer be minted — would have every route correctly gated, while all three status surfaces falsely reported "running without auth". Fixed with one shared authStatus() helper checking both mechanisms.

Stale docs directly contradicting the fix: README (two places), the legacy gate page's hint text, and a CLI help string all described "localhost bypasses auth" as current/intentional. All rewritten to state the actual model.

Minor: llmux auth login printed a raw JSON error blob instead of the parsed message; CHANGELOG [Unreleased] was missing entries for the three merged fixes.

Test plan

  • npm run typecheck / npm run build clean
  • Restarted the live daemon, confirmed banner now reads ✓ auth required — 2 users (sign in at /login), 1 legacy token (no more "(localhost bypasses)")
  • Page footer: ✓ auth required — 2 users
  • GET /health: authEnabled: true
  • Cleaned up all test tokens created during verification

🤖 Generated with Claude Code

https://claude.ai/code/session_01Au8T9RPCb6wbgiEecQrBfZ

Multi-pass sweep after fix/loopback-auth-bypass, fix/client-auth-redirect,
and fix/token-user-store-caching landed. Found via a live web-UI
walkthrough and a parallel CLI audit; both converged on the same root bug
independently.

## Auth-status false negative (real bug, not just stale text)

The startup banner, the picker's page footer, and /health's authEnabled
field all called authStore.authEnabled() — which only reflects LEGACY V1
TOKENS. None of them checked v2 users. A fresh v2-only install (the
current default; v1 tokens can no longer be minted) would have every
route correctly gated by isAuthorized(), while the banner/footer/health
all falsely reported "running without auth" / "no auth — anyone on the
network can attach". Backwards from reality, in the operator-facing
security status.

Added one shared authStatus() helper (checks both v1 count and v2 user
count) and wired it into all three call sites. This required making
pickerPage() and printBanner() async (both had exactly one caller,
already inside an async context) — no other blast radius.

Also removed the startup banner's "(localhost bypasses)" claim — true
when written, describes the exact bypass fix/loopback-auth-bypass just
removed.

## Stale docs directly contradicting the security fix

- README.md (two places): described "localhost bypasses auth" as current,
  intentional behavior. Rewrote to state the actual model — local CLI
  usage never makes an HTTP request (so needs no token); any HTTP/WS
  request, from anywhere, needs a real credential.
- gatePage()'s hint text: said "Generate a token on the daemon host:
  `llmux token create`" and "Localhost bypasses this gate" — both wrong.
  `llmux token create` mints v2-only tokens now, which this v1-only-
  validating legacy form rejects; and there's no bypass anymore. Points
  at /login instead, the actual working path.
- client.ts's per-command help boilerplate: "LLMUX_TOKEN ... not required
  for localhost" — same stale claim, in a currently-unreachable (global
  --help intercepts first) but still-constructed help string.
- CHANGELOG.md [Unreleased]: was empty; added entries for all three
  merged security fixes plus this one.

## Minor: CLI login error surfacing

`llmux auth login` with a wrong passphrase printed the raw JSON response
body: `auth login failed: {"error":"invalid credentials"} (HTTP 401)`.
v2/auth/login.ts now parses the body and extracts `.error` when present,
falling back to the raw text only if it's genuinely not JSON.

## Verification

typecheck + build clean. Restarted the live daemon and confirmed live:
- Startup banner: `✓ auth required — 2 users (sign in at /login), 1
  legacy token` (no more "(localhost bypasses)")
- Page footer: `✓ auth required — 2 users`
- GET /health: `authEnabled: true`
(This instance has both a legacy v1 token and v2 users, so it already
exercised the old code path's "true" branch — the actual fix is for the
v2-only-install case, which the shared helper now also gets right.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Au8T9RPCb6wbgiEecQrBfZ
@steve-krisjanovs steve-krisjanovs merged commit ec3916c into main Jul 1, 2026
1 check passed
@steve-krisjanovs steve-krisjanovs deleted the fix/sweep-auth-status-and-docs branch July 1, 2026 15:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant