A Python port of the Invisible Internet Project (I2P)
1#!/usr/bin/env python3
2"""Check security scan results and fail on CRITICAL or HIGH findings.
3
4Parses bandit JSON output and semgrep JSON output, reports findings,
5and exits non-zero if any CRITICAL or HIGH severity issues are found.
6
7Usage:
8 python tools/security/check_results.py \
9 --bandit bandit-results.json \
10 --semgrep semgrep-results.json \
11 [--pip-audit pip-audit-results.json]
12"""
13from __future__ import annotations
14
15import argparse
16import json
17import sys
18from pathlib import Path
19
20
21def check_bandit(path: Path) -> list[dict]:
22 """Parse bandit JSON results, return HIGH/CRITICAL findings."""
23 if not path.exists():
24 print(f"WARNING: bandit results not found at {path}")
25 return []
26
27 data = json.loads(path.read_text())
28 results = data.get("results", [])
29 serious = []
30
31 for r in results:
32 sev = r.get("issue_severity", "").upper()
33 conf = r.get("issue_confidence", "").upper()
34 if sev in ("HIGH", "CRITICAL") and conf in ("HIGH", "MEDIUM"):
35 serious.append(r)
36
37 return serious
38
39
40def check_semgrep(path: Path) -> list[dict]:
41 """Parse semgrep JSON results, return ERROR-level findings."""
42 if not path.exists():
43 print(f"WARNING: semgrep results not found at {path}")
44 return []
45
46 data = json.loads(path.read_text())
47 results = data.get("results", [])
48 serious = []
49
50 for r in results:
51 sev = r.get("extra", {}).get("severity", "").upper()
52 if sev == "ERROR":
53 serious.append(r)
54
55 return serious
56
57
58def check_pip_audit(path: Path) -> list[dict]:
59 """Parse pip-audit JSON results, return all vulnerabilities."""
60 if not path.exists():
61 print(f"WARNING: pip-audit results not found at {path}")
62 return []
63
64 data = json.loads(path.read_text())
65 # pip-audit JSON format: {"dependencies": [...], "fixes": [...]}
66 deps = data.get("dependencies", []) if isinstance(data, dict) else data
67 # Skip build-tool packages — these aren't our project dependencies
68 build_tools = {"pip", "setuptools", "wheel", "hatchling"}
69 serious = []
70 for dep in deps:
71 if dep.get("name", "").lower() in build_tools:
72 continue
73 vulns = dep.get("vulns", [])
74 if vulns:
75 serious.append(dep)
76
77 return serious
78
79
80def main() -> int:
81 parser = argparse.ArgumentParser(description="Check security scan results")
82 parser.add_argument("--bandit", type=Path, help="Path to bandit JSON results")
83 parser.add_argument("--semgrep", type=Path, help="Path to semgrep JSON results")
84 parser.add_argument("--pip-audit", type=Path, help="Path to pip-audit JSON results")
85 args = parser.parse_args()
86
87 total_issues = 0
88
89 if args.bandit:
90 findings = check_bandit(args.bandit)
91 if findings:
92 print(f"\n=== BANDIT: {len(findings)} HIGH/CRITICAL findings ===")
93 for f in findings:
94 print(f" {f['issue_severity']}/{f['issue_confidence']}: "
95 f"{f['issue_text']}")
96 print(f" {f['filename']}:{f['line_number']}")
97 total_issues += len(findings)
98 else:
99 print("BANDIT: No HIGH/CRITICAL findings")
100
101 if args.semgrep:
102 findings = check_semgrep(args.semgrep)
103 if findings:
104 print(f"\n=== SEMGREP: {len(findings)} ERROR findings ===")
105 for f in findings:
106 loc = f.get("path", "?")
107 line = f.get("start", {}).get("line", "?")
108 msg = f.get("extra", {}).get("message", f.get("check_id", "?"))
109 print(f" ERROR: {msg}")
110 print(f" {loc}:{line}")
111 total_issues += len(findings)
112 else:
113 print("SEMGREP: No ERROR findings")
114
115 if args.pip_audit:
116 findings = check_pip_audit(args.pip_audit)
117 if findings:
118 print(f"\n=== PIP-AUDIT: {len(findings)} vulnerable packages ===")
119 for dep in findings:
120 name = dep.get("name", "?")
121 ver = dep.get("version", "?")
122 for v in dep.get("vulns", []):
123 vid = v.get("id", "?")
124 desc = v.get("description", "")[:100]
125 print(f" {name}=={ver}: {vid} — {desc}")
126 total_issues += len(findings)
127 else:
128 print("PIP-AUDIT: No vulnerabilities found")
129
130 if total_issues > 0:
131 print(f"\nFAILED: {total_issues} security issue(s) found")
132 return 1
133
134 print("\nPASSED: No critical security issues")
135 return 0
136
137
138if __name__ == "__main__":
139 sys.exit(main())