diff --git a/guarddog_nexus/constants.py b/guarddog_nexus/constants.py index 464e46f..71132eb 100644 --- a/guarddog_nexus/constants.py +++ b/guarddog_nexus/constants.py @@ -32,6 +32,7 @@ METADATA_PATTERNS = ( # --------------------------------------------------------------------------- DEFAULT_ECOSYSTEM = "pypi" +SUPPORTED_ECOSYSTEMS = frozenset({"pypi", "go", "npm"}) # --------------------------------------------------------------------------- # Severity diff --git a/guarddog_nexus/routes/api_findings.py b/guarddog_nexus/routes/api_findings.py index b232686..0150780 100644 --- a/guarddog_nexus/routes/api_findings.py +++ b/guarddog_nexus/routes/api_findings.py @@ -13,7 +13,7 @@ from ..constants import ( ) from ..db.engine import get_session from ..db.models import Finding -from ..schemas import FindingsListResponse +from ..schemas import FindingsListResponse, serialize_finding router = APIRouter(prefix="/api/v1/findings", tags=["findings"]) @@ -42,14 +42,5 @@ async def list_findings( "total": total, "limit": limit, "offset": offset, - "findings": [ - { - "id": f.id, - "scan_id": f.scan_id, - **f.data, - "report": f.report, - "created_at": f.created_at.isoformat() if f.created_at else None, - } - for f in findings - ], + "findings": [serialize_finding(f) for f in findings], } diff --git a/guarddog_nexus/routes/api_packages.py b/guarddog_nexus/routes/api_packages.py index bc98651..791d37b 100644 --- a/guarddog_nexus/routes/api_packages.py +++ b/guarddog_nexus/routes/api_packages.py @@ -20,7 +20,7 @@ from ..core.nexus import parse_package_path from ..db.engine import get_session from ..db.models import Scan from ..db.queries import build_package_list_query -from ..schemas import PackageDetailOut, PackageListResponse +from ..schemas import PackageDetailOut, PackageListResponse, serialize_finding router = APIRouter(prefix="/api/v1/packages", tags=["packages"]) @@ -145,7 +145,7 @@ async def get_package( all_findings: list[dict] = [] for s in scans: for f in s.findings: - all_findings.append({"id": f.id, **f.data, "report": f.report}) + all_findings.append(serialize_finding(f)) return { "name": scans[0].package_name, diff --git a/guarddog_nexus/routes/api_scans.py b/guarddog_nexus/routes/api_scans.py index e61021f..980ef9f 100644 --- a/guarddog_nexus/routes/api_scans.py +++ b/guarddog_nexus/routes/api_scans.py @@ -19,7 +19,7 @@ from ..constants import ( from ..db.engine import get_session from ..db.models import Scan from ..db.queries import build_scan_list_query, get_dashboard_stats -from ..schemas import ScanDetailOut, ScanListResponse, StatsResponse +from ..schemas import ScanDetailOut, ScanListResponse, StatsResponse, serialize_finding router = APIRouter(prefix="/api/v1/scans", tags=["scans"]) @@ -171,5 +171,5 @@ async def get_scan(scan_id: int, session: AsyncSession = Depends(get_session)) - "error_message": scan.error_message, "initiator": scan.initiator, "source_ip": scan.source_ip, - "findings": [{"id": f.id, **f.data, "report": f.report} for f in scan.findings], + "findings": [serialize_finding(f) for f in scan.findings], } diff --git a/guarddog_nexus/routes/webhooks.py b/guarddog_nexus/routes/webhooks.py index 520b8e2..27ce5e8 100644 --- a/guarddog_nexus/routes/webhooks.py +++ b/guarddog_nexus/routes/webhooks.py @@ -159,40 +159,45 @@ async def nexus_webhook( async def _scan_component(repository: str, name: str, version: str, ecosystem: str): - from ..core.nexus import nexus_get - - params = urlencode( - { - "repository": repository, - "name": name, - "version": version, - "format": ecosystem, - } - ) - api_path = f"/service/rest/v1/search?{params}" try: - resp = await nexus_get(api_path) - resp.raise_for_status() - data = resp.json() + from ..core.nexus import nexus_get + + params = urlencode( + { + "repository": repository, + "name": name, + "version": version, + "format": ecosystem, + } + ) + api_path = f"/service/rest/v1/search?{params}" + try: + resp = await nexus_get(api_path) + resp.raise_for_status() + data = resp.json() + except Exception as e: + log.warning("Component lookup error for %s==%s: %s", name, version, e) + return + + items = data.get("items", []) + if not items: + log.warning("No items found in search for %s==%s", name, version) + return + + for item in items: + for asset in item.get("assets", []): + asset_path = _extract_asset_path(asset) + if not asset_path or not _is_package_asset(asset_path): + continue + download_url = asset.get("downloadUrl") or _build_download_url( + repository, asset_path + ) + log.info("Scanning component asset: %s", asset_path) + async for session in get_session(): + await harvest(download_url, repository, ecosystem, asset_path, session) + break except Exception as e: - log.warning("Component lookup error for %s==%s: %s", name, version, e) - return - - items = data.get("items", []) - if not items: - log.warning("No items found in search for %s==%s", name, version) - return - - for item in items: - for asset in item.get("assets", []): - asset_path = _extract_asset_path(asset) - if not asset_path or not _is_package_asset(asset_path): - continue - download_url = asset.get("downloadUrl") or _build_download_url(repository, asset_path) - log.info("Scanning component asset: %s", asset_path) - async for session in get_session(): - await harvest(download_url, repository, ecosystem, asset_path, session) - break + log.error("Component scan failed for %s==%s: %s", name, version, e) async def _scan_in_background( diff --git a/guarddog_nexus/schemas.py b/guarddog_nexus/schemas.py index eebbed1..e264787 100644 --- a/guarddog_nexus/schemas.py +++ b/guarddog_nexus/schemas.py @@ -100,3 +100,20 @@ class StatsResponse(BaseModel): total_findings: int top_rules: list[dict] latest_scan_at: datetime | None = None + + +# Finding data known fields (prevents **f.data from overwriting id/scan_id) +_FINDING_DATA_FIELDS = ("rule", "severity", "message", "location", "code") + + +def serialize_finding(finding) -> dict: + """Extract known fields from a Finding, preventing data field injection.""" + result = { + "id": finding.id, + "scan_id": finding.scan_id, + "report": finding.report, + "created_at": finding.created_at.isoformat() if finding.created_at else None, + } + for field in _FINDING_DATA_FIELDS: + result[field] = finding.data.get(field, "") + return result diff --git a/guarddog_nexus/web/templates/package_detail.html b/guarddog_nexus/web/templates/package_detail.html index ac49fd5..6705074 100644 --- a/guarddog_nexus/web/templates/package_detail.html +++ b/guarddog_nexus/web/templates/package_detail.html @@ -57,24 +57,9 @@ {% if f.report and f.report.status == "analyzing" %} {% include "_llm_spinner.html" %} {% elif f.report and f.report.verdict %} -
{{ f.report.summary }}
-{{ f.report.analysis }}
-{{ t('llm_disclaimer', request.state.lang) }}
-