fix: race conditions in lock pop, CSV formula injection, serialize_finding None leak, consolidate plans, update docs

This commit is contained in:
Marker689
2026-05-11 22:31:41 +03:00
parent 3f44de1d98
commit 56786c7aef
11 changed files with 251 additions and 488 deletions

View File

@@ -76,21 +76,24 @@ async def harvest(
_url_locks.pop(download_url, None)
return None
active_found = False
async with lock:
try:
# Re-check DB in case another task already created and finished a scan
active = await session.scalar(
select(Scan.id).where(
Scan.nexus_asset_url == download_url,
Scan.status.in_([ScanStatus.PENDING.value, ScanStatus.SCANNING.value]),
)
# Re-check DB in case another task already created and finished a scan
active = await session.scalar(
select(Scan.id).where(
Scan.nexus_asset_url == download_url,
Scan.status.in_([ScanStatus.PENDING.value, ScanStatus.SCANNING.value]),
)
if active:
log.info("Already scanning this URL, skipping")
return None
finally:
async with _url_lock:
_url_locks.pop(download_url, None)
)
if active:
log.info("Already scanning this URL, skipping")
active_found = True
async with _url_lock:
_url_locks.pop(download_url, None)
if active_found:
return None
scan = Scan(
package_name=package_name,

View File

@@ -21,6 +21,7 @@ from ..db.engine import get_session
from ..db.models import Scan
from ..db.queries import build_package_list_query
from ..schemas import PackageDetailOut, PackageListResponse, serialize_finding
from .api_scans import _csv_safe
router = APIRouter(prefix="/api/v1/packages", tags=["packages"])
@@ -102,8 +103,8 @@ async def export_packages_csv(
for r in rows:
writer.writerow(
[
r.pkg_name,
r.pkg_ver,
_csv_safe(r.pkg_name),
_csv_safe(r.pkg_ver),
r.ecosystem,
r.repository,
r.last_scan.isoformat() if r.last_scan else "",

View File

@@ -21,6 +21,13 @@ from ..db.models import Scan
from ..db.queries import build_scan_list_query, get_dashboard_stats
from ..schemas import ScanDetailOut, ScanListResponse, StatsResponse, serialize_finding
def _csv_safe(value: str) -> str:
if value and value[0] in "=+-@":
return "'" + value
return value
router = APIRouter(prefix="/api/v1/scans", tags=["scans"])
@@ -112,8 +119,8 @@ async def export_scans_csv(
writer.writerow(
[
s.id,
s.package_name,
s.package_version,
_csv_safe(s.package_name),
_csv_safe(s.package_version),
s.ecosystem,
s.repository,
s.status,
@@ -121,7 +128,7 @@ async def export_scans_csv(
s.flagged,
s.started_at.isoformat() if s.started_at else "",
s.finished_at.isoformat() if s.finished_at else "",
s.error_message or "",
_csv_safe(s.error_message or ""),
s.sha256 or "",
]
)

View File

@@ -262,13 +262,12 @@ async def analyze_finding_htmx(
return _render("_llm_spinner.html", request=request, finding_id=finding_id)
async with lock:
try:
finding.report = {"status": "analyzing"}
await session.commit()
report = await analyze_finding(finding.data)
finally:
async with _llm_lock:
_llm_locks.pop(finding_id, None)
finding.report = {"status": "analyzing"}
await session.commit()
report = await analyze_finding(finding.data)
async with _llm_lock:
_llm_locks.pop(finding_id, None)
if report is None:
finding.report = None

View File

@@ -139,5 +139,5 @@ def serialize_finding(finding) -> dict:
"created_at": finding.created_at.isoformat() if finding.created_at else None,
}
for field in _FINDING_DATA_FIELDS:
result[field] = finding.data.get(field, "")
result[field] = finding.data.get(field) or ""
return result