From d0ccad8bf12f4b931c43c72dcfc0f9ba76780018 Mon Sep 17 00:00:00 2001 From: Yogesh Ojha Date: Fri, 4 Sep 2026 19:46:46 +0530 Subject: [PATCH] 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) Claude-Session: https://claude.ai/code/session_012Wg6h3FMyybV7ZFGWrEou5 --- api/app/services/scan.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/api/app/services/scan.py b/api/app/services/scan.py index debf68d8e..3bee9225f 100644 --- a/api/app/services/scan.py +++ b/api/app/services/scan.py @@ -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,