Files
guarddog-nexus/guarddog_nexus/scanner.py

75 lines
2.3 KiB
Python

"""GuardDog CLI integration via subprocess."""
import json
import shutil
import subprocess
from guarddog_nexus.config import config
from guarddog_nexus.logging_setup import log
GUARDDOG_BIN = shutil.which("guarddog") or "guarddog"
def scan_package(filepath: str, ecosystem: str = "pypi") -> dict:
"""Run guarddog scan on a downloaded package file. Returns parsed JSON output."""
cmd = [
GUARDDOG_BIN, ecosystem, "scan", filepath,
"--output-format", "json",
]
log.info("Running: %s", " ".join(cmd))
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=config.scan_timeout_seconds,
)
except subprocess.TimeoutExpired:
log.error("GuardDog scan timed out for %s", filepath)
return {"issues": [], "errors": ["timeout"]}
except FileNotFoundError:
log.error("GuardDog binary not found at %s", GUARDDOG_BIN)
return {"issues": [], "errors": ["guarddog_not_found"]}
if result.returncode not in (0, 1):
log.error("GuardDog exited %d: %s", result.returncode, result.stderr)
return {"issues": [], "errors": [result.stderr.strip()]}
try:
data = json.loads(result.stdout)
except json.JSONDecodeError:
log.error("GuardDog returned invalid JSON for %s", filepath)
return {"issues": [], "errors": ["json_parse_error"]}
return _normalize_output(data)
def _normalize_output(data: dict) -> dict:
"""Normalize guarddog JSON output across versions into a consistent format.
GuardDog JSON format (varies by version):
{
"results": [{"rule": "...", "severity": "...", "message": "...", "location": "..."}],
"errors": [...]
}
Or simpler:
{"issues": [...], "errors": [...]}
"""
findings = []
for entry in data.get("results", data.get("issues", [])):
if isinstance(entry, dict):
findings.append({
"rule": entry.get("rule", entry.get("id", "unknown")),
"severity": entry.get("severity", "WARNING"),
"message": entry.get("message", entry.get("description", "")),
"location": entry.get("location", entry.get("path", "")),
})
return {
"findings": findings,
"errors": data.get("errors", []),
}