Skip to content

fix(auth): apply session User-Agent binding uniformly across auth surfaces - #2340

Merged
jaylfc merged 1 commit into
devfrom
fix/auth-status-ua-symmetry
Aug 9, 2026
Merged

fix(auth): apply session User-Agent binding uniformly across auth surfaces#2340
jaylfc merged 1 commit into
devfrom
fix/auth-status-ua-symmetry

Conversation

@jaylfc

@jaylfc jaylfc commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Root cause of tonight's PWA refresh loop on the Pi (beta.46), fixed at the class level.

Mechanism: sessions store a SHA-256 hash of the login User-Agent as a stolen-cookie check. The API middleware verifies it on every request; /auth/status and /auth/me did not pass the UA, and the chat/canvas/terminal/web-chat WebSocket handlers did not either. When a browser auto-update rotated the UA string, the session became: authenticated per /auth/status, 401 per every /api/* call. LoginGate's session-expired handler re-checks status, sees authenticated, remounts the shell, the API barrage 401s again - a remount loop every few seconds. The WebSocket side is the same hole inverted: the most sensitive surfaces (terminal PTY) accepted a cookie the APIs reject.

Fix: all six call sites pass the request/websocket User-Agent, so the binding check gives one answer everywhere. UA-less (legacy/script) sessions still validate regardless - semantics unchanged.

Proof: two new route-level tests (login under UA A, query under UA B) FAIL on unfixed routes/auth.py (run demonstrated red before the fix was applied) and pass with it; tests/test_auth.py + both channel-hub suites: 180 passed.

Ops note: the live loop on the Pi was cleared by revoking the stale sessions (backup kept at data/.auth_sessions.bak-ua-loop-*); this PR prevents recurrence and lands with the next release train.

…faces

/auth/status and /auth/me validated sessions without the User-Agent the
API middleware checks, so a session whose UA hash stopped matching (a
browser auto-update rotates the UA string) read authenticated on status
while every /api/* call returned 401. The SPA's LoginGate treats that
contradiction as session-expired, re-checks status, gets authenticated,
remounts the shell, and loops - the PWA refresh loop observed on the
beta.46 deployment. The chat, canvas, terminal and web-chat WebSocket
handlers had the inverse hole: they accepted a cookie the APIs reject.

All six call sites now pass the request's User-Agent, so the stolen-
cookie binding check gives one answer everywhere. Sessions created
without a UA hash continue to validate regardless, unchanged.
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jaylfc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 37 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c93f6bfc-4d42-4f9d-b985-9e2b31f3a59f

📥 Commits

Reviewing files that changed from the base of the PR and between 514be45 and 11d7009.

📒 Files selected for processing (7)
  • changelog.d/fix-auth-status-ua-symmetry.md
  • tests/test_auth.py
  • tinyagentos/routes/auth.py
  • tinyagentos/routes/canvas.py
  • tinyagentos/routes/channel_hub.py
  • tinyagentos/routes/chat.py
  • tinyagentos/routes/terminal.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

@kilo-code-bot

kilo-code-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (7 files)
  • changelog.d/fix-auth-status-ua-symmetry.md
  • tests/test_auth.py
  • tinyagentos/routes/auth.py
  • tinyagentos/routes/canvas.py
  • tinyagentos/routes/channel_hub.py
  • tinyagentos/routes/chat.py
  • tinyagentos/routes/terminal.py

Reviewed by step-3.7-flash · Input: 83.8K · Output: 18.4K · Cached: 1.4M

@jaylfc
jaylfc merged commit a6ac091 into dev Aug 9, 2026
21 checks passed
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