fix(scans): don't 500 the scan list on a malformed execution_config

A scan row whose execution_config was missing target_value/target_type
raised a pydantic ValidationError inside the pagination transformer, so
one bad row returned 500 for the entire /scans page. _resolved_config
now drops the offending keys, fills the identity fields and rebuilds,
logging a warning instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Wg6h3FMyybV7ZFGWrEou5
This commit is contained in:
Yogesh Ojha
2026-09-04 19:46:46 +05:30
co-authored by Claude Opus 5
parent 47d788df41
commit d0ccad8bf1
+13 -1
View File
@@ -6,6 +6,7 @@ from typing import Literal
from uuid import UUID
from fastapi import HTTPException, status
from pydantic import ValidationError
from sqlalchemy import Select, case, exists, func, nullslast, or_, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import aliased
@@ -88,6 +89,17 @@ _STATUS_RANK = case(
)
def _resolved_config(scan_id: UUID, masked: dict) -> ResolvedScanConfig:
try:
return ResolvedScanConfig(**masked)
except ValidationError as exc:
bad = {str(e["loc"][0]) for e in exc.errors() if e["loc"]}
logger.warning("scan %s has an unusable execution_config: %s", scan_id, bad)
safe = {"target_value": "", "target_type": ""}
safe.update({k: v for k, v in masked.items() if k not in bad})
return ResolvedScanConfig(**safe)
def _mask_config_headers(config: dict) -> dict:
out = copy.deepcopy(config)
headers = out.get("headers") or {}
@@ -1129,7 +1141,7 @@ class ScanService:
cfg.pop("_auth_header_names", None)
auth = cfg.pop("_auth", None) or {"auth_type": "none"}
masked = _mask_config_headers(cfg)
resolved = ResolvedScanConfig(**masked)
resolved = _resolved_config(scan.id, masked)
return ScanRead(
id=scan.id,
project_id=scan.project_id,