test(tradein/auth): правило про синхронную сверку — сторожем, а не комментарием (#2715) #2735

Merged
bot-backend merged 2 commits from test/2715-verify-password-call-site-guard into main 2026-08-06 14:35:33 +00:00
Collaborator

Summary

Пункт 3 хвоста #2715. Только тест, кода приложения не трогает — поэтому отдельно от #2734 (пункты 1-2), файлы не пересекаются, порядок мержа любой.

Правило «из async def зови ТОЛЬКО verify_password_bounded» жило в docstring app/core/password.py, а синхронная verify_password оставалась публичной и импортируемой. Одной строчки asyncio.to_thread(verify_password, …) в будущем коде хватит, чтобы получить вынос в поток вообще без учёта слотов: внешне быстро, а потолок перебора (#2665) и доля на ключ (#2714) тихо исчезают.

AST, а не grep. verify_password поминается в комментариях и docstring'ах (auth.py, config.py) — текстовый поиск краснел бы на них, и сторож пришлось бы ослаблять исключениями. А форму без скобок (to_thread(verify_password, …)) — ровно ту, ради которой сторож и пишется — grep 'verify_password(' не поймал бы вовсе.

Детектор проверен на себе (test_detector_actually_detects): пустой детектор (return False) прошёл бы все файлы app/ и выглядел бы работающим сторожем до первого настоящего нарушения. Тест фиксирует и то, что он ловит, и то, на что не имеет права краснеть.

hash_password сторож НЕ трогает: в team.py:444/568 он синхронный сознательно (ручки аутентифицированные и редкие, анонимно не вызываются), это оговорено в docstring password.py.

Прецедент такого сторожа в репозитории: backend/tests/sql/test_auth_sql_migrations.py.

Test plan

  • pytest tests/test_password_call_sites.py — 2 passed на текущем main (нарушений в app/ сейчас нет)
  • мутация: from app.core.password import verify_password в app/api/v1/team.pyAssertionError: ['app/api/v1/team.py']
  • ruff 0.7.4 (версия из pre-commit) check + format — чисто

Refs #2715

## Summary Пункт 3 хвоста #2715. Только тест, кода приложения не трогает — поэтому отдельно от #2734 (пункты 1-2), файлы не пересекаются, порядок мержа любой. Правило «из `async def` зови ТОЛЬКО `verify_password_bounded`» жило в docstring `app/core/password.py`, а синхронная `verify_password` оставалась публичной и импортируемой. Одной строчки `asyncio.to_thread(verify_password, …)` в будущем коде хватит, чтобы получить вынос в поток вообще без учёта слотов: внешне быстро, а потолок перебора (#2665) и доля на ключ (#2714) тихо исчезают. **AST, а не grep.** `verify_password` поминается в комментариях и docstring'ах (`auth.py`, `config.py`) — текстовый поиск краснел бы на них, и сторож пришлось бы ослаблять исключениями. А форму без скобок (`to_thread(verify_password, …)`) — ровно ту, ради которой сторож и пишется — `grep 'verify_password('` не поймал бы вовсе. **Детектор проверен на себе** (`test_detector_actually_detects`): пустой детектор (`return False`) прошёл бы все файлы `app/` и выглядел бы работающим сторожем до первого настоящего нарушения. Тест фиксирует и то, что он ловит, и то, на что не имеет права краснеть. `hash_password` сторож НЕ трогает: в `team.py:444/568` он синхронный сознательно (ручки аутентифицированные и редкие, анонимно не вызываются), это оговорено в docstring `password.py`. Прецедент такого сторожа в репозитории: `backend/tests/sql/test_auth_sql_migrations.py`. ## Test plan - [x] `pytest tests/test_password_call_sites.py` — 2 passed на текущем `main` (нарушений в `app/` сейчас нет) - [x] мутация: `from app.core.password import verify_password` в `app/api/v1/team.py` → `AssertionError: ['app/api/v1/team.py']` - [x] ruff 0.7.4 (версия из pre-commit) check + format — чисто Refs #2715
bot-backend added 1 commit 2026-08-06 12:40:18 +00:00
test(tradein/auth): правило про синхронную сверку — сторожем, а не комментарием (#2715)
All checks were successful
CI / changes (pull_request) Successful in 9s
CI Trade-In / changes (pull_request) Successful in 11s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 3m15s
1dc3a08563
Пункт 3 хвоста #2715. Правило «из async def зови ТОЛЬКО verify_password_bounded»
жило в docstring app/core/password.py, а синхронная verify_password оставалась
публичной и импортируемой. Одной строчки asyncio.to_thread(verify_password, …)
в будущем коде хватит, чтобы получить вынос в поток вообще без учёта слотов:
внешне быстро, а потолок перебора (#2665) и доля на ключ (#2714) тихо исчезают.

Сторож по AST, а не по grep: verify_password поминается в комментариях и
docstring'ах (auth.py, config.py) — текстовый поиск краснел бы на них, а форму
без скобок (to_thread(verify_password, …)) — ровно ту, ради которой всё и
пишется — не поймал бы вовсе.

Сам детектор проверен на себе (test_detector_actually_detects): пустой детектор
прошёл бы все файлы и выглядел бы рабочим сторожем. Проверено мутацией: импорт
verify_password в app/api/v1/team.py красит тест.

Прецедент такого сторожа: backend/tests/sql/test_auth_sql_migrations.py.

Refs #2715
Light1YT added 1 commit 2026-08-06 14:20:23 +00:00
test(tradein/auth): сторож не должен зеленеть от съехавшей области (#2715)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 3m6s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
3a7dbd4dda
Ревью: `rglob` по несуществующему каталогу не падает — отдаёт пусто, нарушителей
ноль, тест зелёный навсегда. Достаточно переложить файл в подкаталог tests/
(их восемь, и прецедент такого сторожа лежит именно в подкаталоге), чтобы
`parents[1]` уехал и сторож замолчал. Ровно тот класс, от которого детектор уже
защищён (test_detector_actually_detects), а область — нет. Одна строка:
`_OWNER.exists()` в теле теста; проверено мутацией области — краснеет.

Плюс слепые зоны названы и закреплены исполняемо: строковый доступ
`getattr(mod, "verify_password")` и прямой `bcrypt.checkpw` в обход модуля
сторож НЕ ловит. От намеренного обхода он не защищает и не может — только от
нечаянного. Было требованием ревью назвать это в коде, а не подразумевать.

Refs #2715
bot-backend merged commit f0968c8513 into main 2026-08-06 14:35:33 +00:00
bot-backend deleted branch test/2715-verify-password-call-site-guard 2026-08-06 14:35:33 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2735
No description provided.