fix: аудит — 19 фиксов безопасности, надёжности, UI и 16 новых тестов

- S4: bump jinja2>=3.1.4, python-multipart>=0.0.18, httpx>=0.28.0
- S5: _detect_ecosystem — DEFAULT_ECOSYSTEM для неизвестных форматов
- S6: harvester — log.exception() вместо log.error()
- S8: _scan_component — urlencode параметров
- P1: scanner — proc.kill() при таймауте
- P3: api_packages — selectinload(Scan.findings), убран N+1
- P4+P5: утечка _url_locks и _llm_locks при early return
- P6: DB reaper — сброс {'status':'analyzing'} при старте
- UI: htmx-пагинация, фильтры не теряют flagged, 404 с layout
- UI: мобильные таблицы overflow-x, полная стата на дашборде
- UI: i18n статусов в _status_badge, urlencode package_name
- 16 новых тестов: analyze endpoint (6), scanner errors (4),
  webhook signature (2), llm client (4)
This commit is contained in:
Marker689
2026-05-10 10:45:44 +03:00
parent d483a8b21d
commit 1341404568
31 changed files with 575 additions and 152 deletions

View File

@@ -7,6 +7,7 @@ from urllib.parse import unquote
from fastapi import APIRouter, Depends, Query, Response
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from ..constants import (
CSV_MEDIA_TYPE,
@@ -17,7 +18,7 @@ from ..constants import (
MAX_PAGE_SIZE,
)
from ..db.engine import get_session
from ..db.models import Finding, Scan
from ..db.models import Scan
from ..db.queries import build_package_list_query
router = APIRouter(prefix="/api/v1/packages", tags=["packages"])
@@ -88,14 +89,22 @@ async def export_packages_csv(
writer = csv.writer(output)
writer.writerow(
[
"name", "version", "ecosystem", "repository",
"last_scanned_at", "flagged", "total_findings",
"name",
"version",
"ecosystem",
"repository",
"last_scanned_at",
"flagged",
"total_findings",
]
)
for r in rows:
writer.writerow(
[
r.pkg_name, r.pkg_ver, r.ecosystem, r.repository,
r.pkg_name,
r.pkg_ver,
r.ecosystem,
r.repository,
r.last_scan.isoformat() if r.last_scan else "",
bool(r.is_flagged),
r.findings_sum,
@@ -123,6 +132,7 @@ async def get_package(
await session.execute(
select(Scan)
.where(Scan.package_name == pkg_name, Scan.package_version == pkg_version)
.options(selectinload(Scan.findings))
.order_by(Scan.started_at.desc())
)
)
@@ -135,12 +145,7 @@ async def get_package(
all_findings: list[dict] = []
for s in scans:
findings = (
(await session.execute(select(Finding).where(Finding.scan_id == s.id)))
.scalars()
.all()
)
for f in findings:
for f in s.findings:
all_findings.append({"id": f.id, **f.data, "report": f.report})
return {