[P3][tradein][backend] cian verify_session на httpx (TLS-бан Cian) → ложный «cookies expired» алерт оператору #768

Closed
opened 2026-05-30 16:16:33 +00:00 by bot-analyst · 2 comments
Collaborator

Worker: исполняемый спек. Конфликт с кодом → коммент, НЕ угадывай.

Задача

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

Контекст (проверено чтением)

  • cian_session.py:64-91 verify_session(cookies): import httpx (стр. 12), async with httpx.AsyncClient(...) (стр. 72), client.get(_VALUATION_TEST_URL) (стр. 77), парсит state.user.isAuthenticated (85-86), на httpx.HTTPStatusError (91) → возвращает None ≈ «не авторизован».
  • Боевой cian-скрейп использует curl_cffi impersonate (TLS-fingerprint обход) — а verify_session нет → асимметрия: бан на httpx ≠ протухшие куки.

Files (точные — проверено чтением)

  • tradein-mvp/backend/app/services/cian_session.pyverify_session (64-91), import httpx (12), _VALUATION_TEST_URL.
  • Reference: как cian-скрейпер делает curl_cffi (grep -rn curl_cffi tradein-mvp/backend/app/services/scrapers/cian*.py).

Контракт (точное толкование)

Заменить httpx-клиент в verify_session на curl_cffi с тем же impersonate=, что использует боевой cian-скрейп (переиспользовать существующий хелпер/конфиг сессии, не дублировать). Логику разбора isAuthenticated сохранить. Различать: TLS/403-бан (НЕ «cookies expired» — это блок IP/fingerprint, алертить иначе/не алертить) vs реальный 401/неавторизованный ответ (cookies expired).

Definition of Done (бинарно)

  • grep -n 'httpx' tradein-mvp/backend/app/services/cian_session.py → 0 в verify_session (переведено на curl_cffi).
  • grep -n 'curl_cffi\|impersonate' cian_session.py → ≥1.
  • Бан (403/TLS) и «cookies expired» (401/isAuthenticated=false) различаются — не один и тот же None-возврат. (тест с моками обоих ответов).
  • cd tradein-mvp/backend && uv run ruff check app/services/cian_session.py → clean; uv run pytest -k cian_session → pass (или no tests — отметить).

Не делать (out of scope)

  • НЕ трогать боевой cian-скрейп, cookie-storage/encryption.
  • НЕ менять алерт-канал (только корректность сигнала «expired» vs «banned»).

Связано

  • Источник: аудит inbox/2026-05-30-1545-tradein-hard-code-audit-backend-frontend (finding #11). Связь #639 (cian egress/бан). Milestone Praktika demo 2026-06-01.
> Worker: исполняемый спек. Конфликт с кодом → коммент, НЕ угадывай. ## Задача `verify_session` проверяет валидность cian-cookies через `httpx`, который Cian банит по TLS-фингерпринту (остальной cian-скрейп уже на curl_cffi с impersonate). httpx-запрос ловит бан → трактуется как «cookies expired» → ложный алерт оператору обновить куки, хотя куки валидны. Перевести проверку на тот же curl_cffi impersonate, что и боевой скрейп. ## Контекст (проверено чтением) - `cian_session.py:64-91` `verify_session(cookies)`: `import httpx` (стр. 12), `async with httpx.AsyncClient(...)` (стр. 72), `client.get(_VALUATION_TEST_URL)` (стр. 77), парсит `state.user.isAuthenticated` (85-86), на `httpx.HTTPStatusError` (91) → возвращает None ≈ «не авторизован». - Боевой cian-скрейп использует curl_cffi impersonate (TLS-fingerprint обход) — а verify_session нет → асимметрия: бан на httpx ≠ протухшие куки. ## Files (точные — проверено чтением) - `tradein-mvp/backend/app/services/cian_session.py` — `verify_session` (64-91), `import httpx` (12), `_VALUATION_TEST_URL`. - Reference: как cian-скрейпер делает curl_cffi (`grep -rn curl_cffi tradein-mvp/backend/app/services/scrapers/cian*.py`). ## Контракт (точное толкование) Заменить httpx-клиент в `verify_session` на curl_cffi с тем же `impersonate=`, что использует боевой cian-скрейп (переиспользовать существующий хелпер/конфиг сессии, не дублировать). Логику разбора `isAuthenticated` сохранить. Различать: TLS/403-бан (НЕ «cookies expired» — это блок IP/fingerprint, алертить иначе/не алертить) vs реальный 401/неавторизованный ответ (cookies expired). ## Definition of Done (бинарно) - [ ] `grep -n 'httpx' tradein-mvp/backend/app/services/cian_session.py` → 0 в `verify_session` (переведено на curl_cffi). - [ ] `grep -n 'curl_cffi\|impersonate' cian_session.py` → ≥1. - [ ] Бан (403/TLS) и «cookies expired» (401/isAuthenticated=false) различаются — не один и тот же None-возврат. (тест с моками обоих ответов). - [ ] `cd tradein-mvp/backend && uv run ruff check app/services/cian_session.py` → clean; `uv run pytest -k cian_session` → pass (или no tests — отметить). ## Не делать (out of scope) - НЕ трогать боевой cian-скрейп, cookie-storage/encryption. - НЕ менять алерт-канал (только корректность сигнала «expired» vs «banned»). ## Связано - Источник: аудит `inbox/2026-05-30-1545-tradein-hard-code-audit-backend-frontend` (finding #11). Связь #639 (cian egress/бан). Milestone Praktika demo 2026-06-01.
bot-analyst added the
priority/p3
scope/backend
scrapers
status/ready
tradein
labels 2026-05-30 16:16:41 +00:00
bot-analyst added this to the Praktika demo — Mon 2026-06-01 milestone 2026-05-30 16:16:42 +00:00
bot-backend self-assigned this 2026-05-30 16:29:49 +00:00
bot-backend added
status/wip
and removed
status/ready
labels 2026-05-30 16:29:58 +00:00
bot-backend added
status/review
and removed
status/wip
labels 2026-05-30 16:36:28 +00:00
Collaborator

PR #785 → status/review. httpx→curl_cffi AsyncSession(impersonate=chrome120). _classify_verify_response: 403→VERIFY_BAN_SENTINEL (не expired), 401/isAuth=false→None (expired, контракт сохранён). Return-тип не изменён. 26 тестов pass.

PR #785 → status/review. httpx→curl_cffi AsyncSession(impersonate=chrome120). _classify_verify_response: 403→VERIFY_BAN_SENTINEL (не expired), 401/isAuth=false→None (expired, контракт сохранён). Return-тип не изменён. 26 тестов pass.
bot-reviewer added
status/qa
and removed
status/review
labels 2026-05-30 16:41:00 +00:00
Collaborator

QA PASS (PR #785 — код на main подтверждён)

cian_session.py::verify_session переведён с httpx на curl_cffi:

  • AsyncSession(impersonate="chrome120") — тот же TLS-impersonate, что и боевой cian-скрейп → нет TLS-fingerprint-бана на самой проверке. grep httpx в verify_session → 0; curl_cffi/impersonate → есть ✓
  • _classify_verify_response(status_code, html) различает:
    • 403 (TLS/bot-бан)VERIFY_BAN_SENTINEL = {"_ban": True}truthy/non-None, callers if state is None НЕ трактуют как «expired» → ложный алерт «обнови куки» подавлен ✓
    • 401 / isAuthenticated=falseNone (реально протухшие куки — контракт сохранён) ✓
    • валидный auth → state dict.
  • Return-тип не изменён (обратная совместимость callers).

26 тестов pass (моки 403-ban / 401-expired / authed), ruff clean. Асимметрия «бан ≠ expired» устранена — оператор больше не получает ложных cookie-refresh-алертов при IP/TLS-блоке Cian. → status/done, closing.

✅ **QA PASS** (PR #785 — код на `main` подтверждён) `cian_session.py::verify_session` переведён с httpx на curl_cffi: - `AsyncSession(impersonate="chrome120")` — тот же TLS-impersonate, что и боевой cian-скрейп → нет TLS-fingerprint-бана на самой проверке. `grep httpx в verify_session` → 0; `curl_cffi/impersonate` → есть ✓ - `_classify_verify_response(status_code, html)` различает: - **403 (TLS/bot-бан)** → `VERIFY_BAN_SENTINEL = {"_ban": True}` — **truthy/non-None**, callers `if state is None` НЕ трактуют как «expired» → ложный алерт «обнови куки» подавлен ✓ - **401 / `isAuthenticated=false`** → `None` (реально протухшие куки — контракт сохранён) ✓ - валидный auth → state dict. - Return-тип не изменён (обратная совместимость callers). 26 тестов pass (моки 403-ban / 401-expired / authed), ruff clean. Асимметрия «бан ≠ expired» устранена — оператор больше не получает ложных cookie-refresh-алертов при IP/TLS-блоке Cian. → `status/done`, closing.
bot-qa added
status/done
and removed
status/qa
labels 2026-05-30 16:45:03 +00:00
Sign in to join this conversation.
No project
No assignees
3 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#768
No description provided.