Repository navigation
feat(access): password unlock box as a typeable alternative to the token - #16
Merged
Merged
Conversation
APP_PASSWORD (optional, >=12 chars) is accepted anywhere APP_ACCESS_TOKEN is, and the anonymous banner now carries an Unlock box, so the 48-hex token never has to be pasted into a ?token= URL. A memorable password is guessable where the token is not, so wrong passwords are rate-limited per IP (5/15min) and globally (50/h), and a locked-out caller's guesses are not compared. The token is never limited and stays the recovery path. The socket connect previously answered every guess with no charge at all (access_tier); it now goes through the same counters. Fixes found on the way: - static/style.css was never linked by index.html, so the access banner had shipped unstyled. Its rules move into the inline <style>; the dead file goes. - secrets.compare_digest on non-ASCII str raises TypeError: a token with an accented character was a live HTTP 500. Compare bytes. - _enforce_access ran in FastAPI's thread pool, so the anonymous limiter's check-then-append could overshoot under a parallel burst. Now a coroutine. - uvicorn's access log recorded /files/...?token=<credential>; redacted. - engineio hardcodes REMOTE_ADDR=127.0.0.1; socket IP falls back to the scope. - Responses carry X-Auth-Error so the page drops a credential the server stopped honouring instead of showing "unlimited" while metered. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CFynXGnSmofasTt5UDQaMh
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.
What
APP_PASSWORD(≥12 chars), accepted anywhereAPP_ACCESS_TOKENis.?token=URL.Why the limits are required
The socket connect resolved the access tier without charging any request and reported the result in
access_tier. That gave anyone an unlimited way to test guesses. It didn't matter for a 48-hex token, but it does for a password a person can remember.Fixes found along the way
static/style.csswas never linked fromindex.html. Its rules move into the inline<style>, and the dead file is deleted.?token=jalape%C3%B1o→ 500, measured on prod).compare_digestraisesTypeErroron non-ASCIIstr; the code now compares bytes._enforce_accessran in FastAPI's thread pool, so a parallel burst could get past the anonymous and password counters. It's now a coroutine./files/…?token=<credential>. The value is now redacted.REMOTE_ADDR=127.0.0.1, so the socket fallback now reads the client address from the ASGI scope.X-Auth-Errorresponse header, so the page drops a credential the server stopped accepting instead of showing "unlimited" while it's actually limited.Verification
uv run pytest -q: 187 passed, 1 skipped. 19 new tests, none removed.strcomparison, or making the dependency sync again each turns tests red.sessionStorageandsocket.auth; Unlock is disabled during a stream; the correct password removes the banner and survives a reload; an HTTP rejection relocks the page with the real anonymous limits.GET /files/x.wav?token=[redacted].Fly-Client-IPdid not get past the per-IP limit (429 on the 16th of 17 forged requests), so trusting that header first is safe.gpt-6-sol. 5 of 6 findings confirmed and fixed; the IP-spoofing concern was disproved live (above).🤖 Generated with Claude Code
https://claude.ai/code/session_01CFynXGnSmofasTt5UDQaMh