fix(tradein): security hotfix — C-3 config default + C-4 empty estimate persist + C-6 PDF URL allowlist #435
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
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#435
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/tradein-security-hotfix-v2"
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?
Закрывает 3 critical из аудита
audits/TradeIn_MVP_CodeReview_May23.md(40 findings, demo 28.05).Что в PR
C-3 — hardcoded password в default config
File:
tradein-mvp/backend/app/core/config.pydatabase_url = "postgresql+psycopg://tradein:tradein@..."DATABASE_URLenv пустой (fail-fast).docker-compose.prod.ymlуже задаёт через${TRADEIN_POSTGRES_PASSWORD}— прод не сломается.C-4 —
_empty_estimateне сохранялся в БД → 404File:
tradein-mvp/backend/app/services/estimator.py_empty_estimate(payload, db, *, reason)теперь делает INSERT вtrade_in_estimatesсconfidence='low', пустыми arrays,expires_at=+24h.CAST(:id AS uuid)(psycopg v3, не:id::uuid)./estimate/{id}возвращает 200 (не 404), PDF тоже работает.C-6 — PDF URL XSS через
source_urlиphoto_urlsFile:
tradein-mvp/backend/app/services/exporters/trade_in_pdf.py_ALLOWED_URL_DOMAINS(avito.ru/cian.ru/realty.yandex.ru/n1.ru, https) и_ALLOWED_PHOTO_CDN(images.avito.st/cdn-p.cian.site/avatars.mds.yandex.net/n1ru.cdn-cw.com)._safe_url()хелпер парсит scheme + netloc._examples_rows:safe = _safe_url(lot.source_url)— если None, выводится только текст без<a>. Защита отjavascript:alert(1)/data:text/html,...в Chrome PDF viewer.Не в PR (отдельно)
Manual smoke (post-deploy)
POST /api/v1/trade-in/estimateнесуществующий адрес →GET /estimate/{id}→ 200 (не 404).DATABASE_URL→ ValidationError на старте.source_url=javascript:alert(1)→ только текст адреса,<a>нет.Vault
fixes/Fix_TradeIn_SecurityHotfix_May23.mdдобавлен.C-3: убрать hardcoded DATABASE_URL default из Settings (fail-fast если env не задан). C-4: _empty_estimate теперь делает INSERT в БД — GET /estimate/{id} не возвращает 404. C-6: _safe_url allowlist против javascript:/data: в source_url; CDN allowlist для photo_url.Deep Code Review — verdict ✅ APPROVE → merged
Files: 3 (config.py / estimator.py / trade_in_pdf.py). +115 / -10.
Security checks (all PASS)
C-3 (config) —
database_url: strбез default ✅. Pydantic v2 ValidationError при пустом env. Dev compose hardcodes credentials inline (acceptable for dev), prod compose использует${TRADEIN_POSTGRES_PASSWORD:?required}.C-4 (empty_estimate persist) — SQL parameterized через
text() + dict bindings✅,CAST(:id AS uuid)psycopg v3 pattern ✅,db.commit()propagates exception (no silent swallow) ✅. Schema полеconfidence_explanationсуществует вAggregatedEstimate. Только 1 callsite — обновлён.C-6 (PDF URL allowlist) —
_safe_urlчерезurlparse+ strictscheme == "https"+netloc in frozenset(exact match, неendswith). Verified edge cases:javascript:/data:→ BLOCK (scheme≠https) ✅//evil.comscheme-relative → BLOCK ✅http://avito.ru/downgrade → BLOCK ✅https://avito.ru.evil.com/subdomain attack → BLOCK ✅https://avito.ru@evil.com/userinfo bypass → BLOCK (netloc=avito.ru@evil.com, не match exact) ✅https://аvito.ru/→ BLOCK ✅_html.escape(safe)внутри<a href=...>✅🟡 Minor (non-blocking, для отдельного PR)
_safe_url(cdn=True)и_ALLOWED_PHOTO_CDNопределены, но photo_url нигде не вызывает_safe_url— функция мёртвая для cdn-режима. Описание PR говорит "Применён и для photo_urls" — inaccurate. Photo URL XSS в PDF остаётся open (если WeasyPrint embed'ит<img src='javascript:...'>— risk низкий т.к. это атрибут не href, но allowlist стоит применить consistency-ради)._safe_url(приоритет: для pilot OK, добавить post-demo).Deploy paths check ✅
.forgejo/workflows/deploy-tradein.ymlpaths-filter ловитtradein-mvp/backend/**→build-backendjob triggers корректно. Все 3 файла в этом пути.Cross-PR
Merge_base mismatch (отстал от PR #434) безопасен — diff чисто в
tradein-mvp/, не задевает.forgejo/workflows/deploy.ymlилиops/glitchtip-auth-forwarder/**.Merged via deep-code-reviewer.