fix: scanner now handles real guarddog v2 JSON format
This commit is contained in:
@@ -11,10 +11,14 @@ 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."""
|
||||
"""Run guarddog scan on a downloaded package file. Returns normalized dict."""
|
||||
cmd = [
|
||||
GUARDDOG_BIN, ecosystem, "scan", filepath,
|
||||
"--output-format", "json",
|
||||
GUARDDOG_BIN,
|
||||
ecosystem,
|
||||
"scan",
|
||||
filepath,
|
||||
"--output-format",
|
||||
"json",
|
||||
]
|
||||
|
||||
log.info("Running: %s", " ".join(cmd))
|
||||
@@ -28,47 +32,74 @@ def scan_package(filepath: str, ecosystem: str = "pypi") -> dict:
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
log.error("GuardDog scan timed out for %s", filepath)
|
||||
return {"issues": [], "errors": ["timeout"]}
|
||||
return {"findings": [], "errors": ["timeout"]}
|
||||
except FileNotFoundError:
|
||||
log.error("GuardDog binary not found at %s", GUARDDOG_BIN)
|
||||
return {"issues": [], "errors": ["guarddog_not_found"]}
|
||||
return {"findings": [], "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()]}
|
||||
return {"findings": [], "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 {"findings": [], "errors": ["json_parse_error"]}
|
||||
|
||||
return _normalize_output(data)
|
||||
|
||||
|
||||
def _normalize_output(data: dict) -> dict:
|
||||
"""Normalize guarddog JSON output across versions into a consistent format.
|
||||
"""Normalize guarddog JSON into consistent format.
|
||||
|
||||
GuardDog JSON format (varies by version):
|
||||
{
|
||||
"results": [{"rule": "...", "severity": "...", "message": "...", "location": "..."}],
|
||||
"errors": [...]
|
||||
}
|
||||
Or simpler:
|
||||
{"issues": [...], "errors": [...]}
|
||||
GuardDog v2 JSON:
|
||||
{"package": "...", "issues": N, "errors": {}, "results": {"rule": null|{}|str|list}}
|
||||
Rules mapped as:
|
||||
- null → not applicable, skip
|
||||
- {} → active but no findings, skip
|
||||
- str → metadata finding (description)
|
||||
- list → semgrep findings [{message, location, code}]
|
||||
"""
|
||||
findings = []
|
||||
results = data.get("results", {})
|
||||
|
||||
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", "")),
|
||||
})
|
||||
if isinstance(results, list):
|
||||
results = {}
|
||||
|
||||
for rule_name, value in results.items():
|
||||
if value is None:
|
||||
continue
|
||||
if isinstance(value, str):
|
||||
findings.append(
|
||||
{
|
||||
"rule": rule_name,
|
||||
"severity": "WARNING",
|
||||
"message": value,
|
||||
"location": "",
|
||||
}
|
||||
)
|
||||
elif isinstance(value, list):
|
||||
for item in value:
|
||||
if isinstance(item, dict):
|
||||
findings.append(
|
||||
{
|
||||
"rule": rule_name,
|
||||
"severity": item.get("severity", "WARNING"),
|
||||
"message": item.get("message", ""),
|
||||
"location": item.get("location", ""),
|
||||
}
|
||||
)
|
||||
elif isinstance(value, dict) and not value:
|
||||
continue
|
||||
|
||||
errors = data.get("errors", {})
|
||||
if isinstance(errors, dict):
|
||||
errors_list = [f"{k}: {v}" for k, v in errors.items() if v]
|
||||
else:
|
||||
errors_list = errors if isinstance(errors, list) else []
|
||||
|
||||
return {
|
||||
"findings": findings,
|
||||
"errors": data.get("errors", []),
|
||||
"errors": errors_list,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user