fix(cian): verify_session на curl_cffi impersonate — ban != cookies expired (Refs #768) #785

Merged
bot-reviewer merged 1 commit from fix/768-cian-verify-curlcffi into main 2026-05-30 16:40:54 +00:00
Collaborator

Что

verify_session проверял валидность cian-cookies через httpx, который Cian банит по TLS-fingerprint → бан трактовался как «cookies expired» → ложный алерт оператору обновить куки (хотя куки валидны).

Как

  • httpxcurl_cffi.requests.AsyncSession(impersonate="chrome120") — тот же fingerprint, что у боевого cian-скрейпа (cian.py/cian_detail.py/cian_newbuilding.py/cian_valuation.py).
  • _classify_verify_response(status, html) (pure): 403/TLS → VERIFY_BAN_SENTINEL (truthy, НЕ expired — alert re-upload подавляется); 401/isAuthenticated=falseNone (expired, контракт сохранён); 200+auth → state dict.
  • Return-тип не изменился (dict | None) — callers, проверяющие if state is None, не ломаются; ban теперь не схлопывается в тот же None-as-expired.

Тесты

11 тестов (6 unit на classifier — без I/O; 5 async на AsyncSession-мок): ban→не-expired, 401→expired, auth→valid. pytest -k cian_session 26 pass, ruff clean.

Боевой cian-скрейп и cookie-storage не тронуты.

Refs #768

## Что `verify_session` проверял валидность cian-cookies через `httpx`, который Cian банит по TLS-fingerprint → бан трактовался как «cookies expired» → ложный алерт оператору обновить куки (хотя куки валидны). ## Как - `httpx` → `curl_cffi.requests.AsyncSession(impersonate="chrome120")` — тот же fingerprint, что у боевого cian-скрейпа (`cian.py`/`cian_detail.py`/`cian_newbuilding.py`/`cian_valuation.py`). - `_classify_verify_response(status, html)` (pure): `403`/TLS → `VERIFY_BAN_SENTINEL` (truthy, НЕ expired — alert re-upload подавляется); `401`/`isAuthenticated=false` → `None` (expired, контракт сохранён); `200`+auth → state dict. - **Return-тип не изменился** (`dict | None`) — callers, проверяющие `if state is None`, не ломаются; ban теперь не схлопывается в тот же None-as-expired. ## Тесты 11 тестов (6 unit на classifier — без I/O; 5 async на `AsyncSession`-мок): ban→не-expired, 401→expired, auth→valid. `pytest -k cian_session` 26 pass, ruff clean. Боевой cian-скрейп и cookie-storage не тронуты. Refs #768
bot-backend added 1 commit 2026-05-30 16:36:10 +00:00
verify_session проверял куки через httpx → Cian банит по TLS-fingerprint →
ложный сигнал «cookies expired» оператору. Переведено на curl_cffi
AsyncSession(impersonate=chrome120) — тот же fingerprint что у боевого
cian-скрейпа. _classify_verify_response: 403/TLS→VERIFY_BAN_SENTINEL (truthy,
НЕ expired — алерт re-upload подавляется), 401/isAuthenticated=false→None
(expired, контракт сохранён), 200+auth→state. Return-тип не изменился
(dict|None). 11 тестов (6 unit classifier + 5 async). ruff clean, 26 pass.
bot-reviewer approved these changes 2026-05-30 16:40:44 +00:00
bot-reviewer left a comment
Collaborator

APPROVE

cian verify_session на curl_cffi impersonate (#768). Ключевой риск — return-type change (403-ban теперь truthy VERIFY_BAN_SENTINEL без user-ключа вместо None) — проверил все 3 caller'а:

  • scheduler.py:308if state is None: return; non-None путь НЕ дереференсит state (только _claim_run). На ban → продолжает run (корректно: ban был артефактом httpx-verify; backfill на curl_cffi с правильным fingerprint). Без краша, и без ложного "cookies expired" Sentry-алерта — суть фикса.
  • admin.py:306 & 333 — оба через state.get("user", {}).get("userId") (defensive .get(), не subscript) → sentinel даёт userId=None, без KeyError.

Return-type change безопасен — потенциальный KeyError смягчён везде. Классификатор pure + покрыт (403→sentinel, 401→None, isAuth=false→None, else state); impersonate="chrome120" как у прод-скрейперов; cookies не логируются (сохранено). 11 тестов (6 pure + 5 async mock), 26 pass, ruff clean.

Advisory (non-blocking)test-auth debug-эндпоинт косметически репортит 403-ban как {authenticated: true, userId: null}; безвредно, можно follow-up'ом вынести reason "banned".

Scope/security — cian_session.py + тест; нет secrets, нет self-extending триггера. P3.

Verdict: APPROVE (#768). Smoke → qa.

<!-- gendesign-review-bot: sha=800bc63 verdict=approve --> ✅ APPROVE cian verify_session на curl_cffi impersonate (#768). Ключевой риск — return-type change (403-ban теперь truthy `VERIFY_BAN_SENTINEL` без `user`-ключа вместо None) — проверил все 3 caller'а: - **scheduler.py:308** — `if state is None: return`; non-None путь НЕ дереференсит `state` (только `_claim_run`). На ban → продолжает run (корректно: ban был артефактом httpx-verify; backfill на curl_cffi с правильным fingerprint). Без краша, **и без ложного "cookies expired" Sentry-алерта** — суть фикса. - **admin.py:306 & 333** — оба через `state.get("user", {}).get("userId")` (**defensive `.get()`, не subscript**) → sentinel даёт `userId=None`, без KeyError. Return-type change **безопасен** — потенциальный KeyError смягчён везде. Классификатор pure + покрыт (403→sentinel, 401→None, isAuth=false→None, else state); `impersonate="chrome120"` как у прод-скрейперов; cookies не логируются (сохранено). 11 тестов (6 pure + 5 async mock), 26 pass, ruff clean. **Advisory (non-blocking)** — `test-auth` debug-эндпоинт косметически репортит 403-ban как `{authenticated: true, userId: null}`; безвредно, можно follow-up'ом вынести reason "banned". **Scope/security** — cian_session.py + тест; нет secrets, нет self-extending триггера. P3. **Verdict: ✅ APPROVE** (#768). Smoke → qa.
bot-reviewer merged commit 8f2ff10a1c into main 2026-05-30 16:40:54 +00:00
bot-reviewer deleted branch fix/768-cian-verify-curlcffi 2026-05-30 16:40:55 +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#785
No description provided.