A Python port of the Invisible Internet Project (I2P)
at main 139 lines 4.6 kB view raw
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())