fix(tradein): SSRF host-allowlist на admin scrape + удалён dead ADMIN_TOKEN (Refs #756) #778
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#778
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/756-admin-ssrf-allowlist"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Что
Два остаточных пункта (RBAC сам по себе OK —
main.py:135энфорсит admin-роль, НЕ тронут):*_urlиз query без host-проверки (defense-in-depth поверх RBAC).ADMIN_TOKENобъявлен в compose/DEPLOY, но не читается кодом.Как
_assert_allowed_url(url)вadmin.py: абсолютный URL с не-allowlist хостом →HTTPException(400); относительный путь (/catalog/...) → ok (хост подставляется источником downstream).scrape_avito_house,scrape_avito_detail,scrape_yandex_detail,scrape_cian_detail,scrape_cian_newbuilding.settings.scrape_allowed_hostsвconfig.py— сверено сscrapers/*.py: к дефолту добавленыekb.cian.ru(cian.py base_url) иekaterinburg.n1.ru(n1.py base_url), иначе сломался бы легитимный scrape.ADMIN_TOKEN(0 чтений вbackend/app) убран изdocker-compose.prod.yml+DEPLOY.md.main.pyRBAC не тронут. Тестtests/test_admin_ssrf.py(11 кейсов:169.254.169.254→ 400, относительный путь → ok).pytest -k "admin and (ssrf or allowlist)"11 passed, ruff clean.Refs #756
✅ APPROVE
SSRF host-allowlist + dead ADMIN_TOKEN removal (security #756). Проверил как security-sensitive.
Correctness
_assert_allowed_url:urlparse→ еслиnetlocнепустой и не вscrape_allowed_hosts→ 400; относительные пути (пустой netloc) проходят. Применён ко ВСЕМ 5 хендлерам с внешним*_url(avito_house/detail, yandex_detail, cian_detail, cian_newbuilding) — тщательно, шире 2 названных в issue.169.254.169.254(metadata),localhost,evil.example.com. Userinfo-трюкavito.ru@evil.com→ netloc включаетevil.com→ блок. Protocol-relative//evil.com→ блок. Solid.ekb.cian.ru+ekaterinburg.n1.ru(реальные base-хосты скрейперов) — worker сверил со scrapers/*.py; over-block = мягкий 400 на debug-эндпоинте, не data loss.X-Authenticated-User(main.py:135) не тронут. Удалён placeholder<token или пусто>— не реальный secret, нет security-tripwire.Tests — 11 unit-кейсов на чистый helper (без DB/scraper; guard падает первым), вкл. allowlist-integrity. 11 passed, ruff clean.
Scope/self-extending — admin.py/config.py/compose/DEPLOY.md/тест; ни один не auto-merge-policy/CLAUDE.md/persona/_autonomous_pickup. main.py RBAC не тронут по scope. Литерального secret в диффе нет.
Verdict: ✅ APPROVE (Praktika security #756). Advisory: QA проверить что легитимный avito/cian/yandex/n1 scrape проходит allowlist.