Skip to content

fix(oauth): add state to OAuth callback flow to prevent Login-CSRF - #252

Merged
ArtVsMark merged 3 commits into
mainfrom
claude/issue-review-planning-yiptra
Jul 10, 2026
Merged

fix(oauth): add state to OAuth callback flow to prevent Login-CSRF#252
ArtVsMark merged 3 commits into
mainfrom
claude/issue-review-planning-yiptra

Conversation

@ArtVsMark

Copy link
Copy Markdown
Owner

Что и зачем

Локальный OAuth callback-сервер (wait_for_auth_code/_make_oauth_handler) принимал первый пришедший ?code=... без проверки, что колбэк действительно пришёл в ответ на именно этот authorize-редирект. Страница, заманившая жертву перейти на http://localhost:<port>/callback?code=<код атакующего>, могла привязать локальное приложение к аккаунту атакующего на Stepik (Login-CSRF, confirmed Medium-severity finding из security-аудита).

Теперь:

  • authorize_via_browser() генерирует криптографически случайный state (secrets.token_urlsafe(32)) и добавляет его в authorize URL.
  • wait_for_auth_code() / _make_oauth_handler() принимают обязательный параметр expected_state и отклоняют колбэк с отсутствующим/несовпадающим state (RuntimeError, код не извлекается вовсе).

Closes #241

Тип изменения

  • fix — исправление бага

Чеклист

  • Ветка от свежего main, PR — в main
  • pytest tests/ -x -q — зелёные (996 passed)
  • ruff check . и ruff format --check . — чисто
  • mypy src/stepik_grader --ignore-missing-imports — чисто
  • Новые функции: type hints + docstring (изменения — в существующих модулях)
  • Версия не правится вручную
  • CHANGELOG.md обновлён (запись под ### Fixed)
  • Нет секретов в диффе

Как проверял

pytest tests/ -x -q --tb=short   # 996 passed
ruff check .                      # All checks passed!
ruff format --check .             # чисто
mypy src/stepik_grader --ignore-missing-imports   # Success: no issues found

Новые regression-тесты:

  • tests/test_oauth_flow.py::TestWaitForAuthCode / TestOAuthHandler — отдельные тесты на mismatch/missing state, happy path обновлён под новую сигнатуру.
  • tests/test_stepik_client_extra.py::TestAuthorizeViaBrowserState — authorize URL содержит тот же state, что уходит в wait_for_auth_code; state меняется между вызовами.

Generated by Claude Code

claude added 3 commits July 10, 2026 07:23
ZIP/GitHub test-case links extracted from a task's HTML text were fetched
through the same authenticated requests.Session used for the Stepik API,
sending the Bearer token to any domain the task text happened to link to.

External downloads (GitHub always, ZIP for non-stepik.org hosts) now go
through core/stepik_client.py::external_download_get() - a fresh session
with no Authorization header, gated by validate_external_url() against an
explicit host allowlist (github.com, raw.githubusercontent.com,
api.github.com, codeload.github.com) with loopback/private/link-local IP
literals rejected outright. Genuine stepik.org ZIP links still use the
authenticated session via is_stepik_url() since that's first-party, not a
leak. _download_github_tests() no longer takes a session parameter at all.

Closes #240 (security audit finding F-01, part of #146/#97).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JygXwmtDyBLzCaBa4opDcp
The loopback OAuth callback server accepted the first ?code=... it saw
with no verification that the request actually originated from the
authorize redirect it triggered. A page that lured the victim into
hitting http://localhost:<port>/callback?code=<attacker's code> could
bind the local app to the attacker's Stepik account.

authorize_via_browser() now sends a cryptographically random state
(secrets.token_urlsafe(32)) in the authorize URL. wait_for_auth_code()
and _make_oauth_handler() take a required expected_state parameter and
reject a missing/mismatched state with a clear RuntimeError before ever
extracting a code.

Closes #241 (security audit finding F-02, part of #146/#149/#97).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JygXwmtDyBLzCaBa4opDcp
@ArtVsMark
ArtVsMark marked this pull request as ready for review July 10, 2026 07:40
@ArtVsMark
ArtVsMark merged commit d1fe687 into main Jul 10, 2026
10 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.

fix: security(oauth): добавить state в OAuth callback flow

2 participants