fix(security): auth-status reporting, stale docs, error surfacing#80
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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'sauthEnabledfield 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 sharedauthStatus()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 loginprinted 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 buildclean✓ auth required — 2 users (sign in at /login), 1 legacy token(no more "(localhost bypasses)")✓ auth required — 2 usersGET /health:authEnabled: true🤖 Generated with Claude Code
https://claude.ai/code/session_01Au8T9RPCb6wbgiEecQrBfZ