1717
1818class SecurityScanner :
1919 """Comprehensive security scanner for the project."""
20-
20+
2121 def __init__ (self , project_root : Path ):
2222 """Initialize security scanner."""
2323 self .project_root = project_root
2424 self .results = {}
2525 self .sarif_files = []
26-
26+
2727 def run_bandit (self ) -> Tuple [bool , str ]:
2828 """Run Bandit security linter with SARIF output."""
2929 print ("Running Bandit security analysis..." )
30-
30+
3131 try :
3232 # Check if bandit-sarif-formatter is available
3333 try :
3434 import bandit_sarif_formatter
35+
3536 sarif_available = True
3637 except ImportError :
3738 sarif_available = False
3839 print ("WARNING: bandit-sarif-formatter not available, falling back to JSON" )
39-
40+
4041 # Generate JSON output (always)
41- subprocess .run ([
42- "python" , "-m" , "bandit" , "-r" , "src/" ,
43- "-f" , "json" , "-o" , "bandit-report.json"
44- ], cwd = self .project_root , check = False )
45-
42+ subprocess .run (
43+ ["python" , "-m" , "bandit" , "-r" , "src/" , "-f" , "json" , "-o" , "bandit-report.json" ],
44+ cwd = self .project_root ,
45+ check = False ,
46+ )
47+
4648 # Generate SARIF output if formatter is available
4749 if sarif_available :
48- subprocess .run ([
49- "python" , "-m" , "bandit" , "-r" , "src/" ,
50- "-f" , "sarif" , "-o" , "bandit-report.sarif"
51- ], cwd = self .project_root , check = False )
52-
50+ subprocess .run (
51+ [
52+ "python" ,
53+ "-m" ,
54+ "bandit" ,
55+ "-r" ,
56+ "src/" ,
57+ "-f" ,
58+ "sarif" ,
59+ "-o" ,
60+ "bandit-report.sarif" ,
61+ ],
62+ cwd = self .project_root ,
63+ check = False ,
64+ )
65+
5366 self .sarif_files .append ("bandit-report.sarif" )
5467 print ("SUCCESS: Bandit SARIF report generated for GitHub Security integration" )
55-
68+
5669 return True , "Bandit scan completed"
57-
70+
5871 except Exception as e :
5972 return False , f"Bandit scan failed: { e } "
60-
73+
6174 def run_safety (self ) -> Tuple [bool , str ]:
6275 """Run Safety dependency vulnerability check."""
6376 print ("Running Safety dependency scan..." )
64-
77+
6578 try :
66- result = subprocess .run ([
67- "python" , "-m" , "safety" , "check" , "--json"
68- ], cwd = self .project_root , capture_output = True , text = True )
69-
79+ result = subprocess .run (
80+ ["python" , "-m" , "safety" , "check" , "--json" ],
81+ cwd = self .project_root ,
82+ capture_output = True ,
83+ text = True ,
84+ )
85+
7086 with open (self .project_root / "safety-report.json" , "w" ) as f :
7187 f .write (result .stdout )
72-
88+
7389 return True , "Safety scan completed"
74-
90+
7591 except Exception as e :
7692 return False , f"Safety scan failed: { e } "
77-
93+
7894 def run_trivy (self ) -> Tuple [bool , str ]:
7995 """Run Trivy container vulnerability scan."""
8096 print ("Running Trivy container security scan..." )
81-
97+
8298 try :
8399 # Build image
84- subprocess .run ([
85- "docker" , "build" , "-t" , "security-scan:latest" , "."
86- ], cwd = self .project_root , check = True )
87-
100+ subprocess .run (
101+ ["docker" , "build" , "-t" , "security-scan:latest" , "." ],
102+ cwd = self .project_root ,
103+ check = True ,
104+ )
105+
88106 # SARIF output
89- subprocess .run ([
90- "trivy" , "image" , "--format" , "sarif" ,
91- "--output" , "trivy-results.sarif" ,
92- "security-scan:latest"
93- ], cwd = self .project_root , check = False )
94-
107+ subprocess .run (
108+ [
109+ "trivy" ,
110+ "image" ,
111+ "--format" ,
112+ "sarif" ,
113+ "--output" ,
114+ "trivy-results.sarif" ,
115+ "security-scan:latest" ,
116+ ],
117+ cwd = self .project_root ,
118+ check = False ,
119+ )
120+
95121 # JSON output
96- subprocess .run ([
97- "trivy" , "image" , "--format" , "json" ,
98- "--output" , "trivy-results.json" ,
99- "security-scan:latest"
100- ], cwd = self .project_root , check = False )
101-
122+ subprocess .run (
123+ [
124+ "trivy" ,
125+ "image" ,
126+ "--format" ,
127+ "json" ,
128+ "--output" ,
129+ "trivy-results.json" ,
130+ "security-scan:latest" ,
131+ ],
132+ cwd = self .project_root ,
133+ check = False ,
134+ )
135+
102136 self .sarif_files .append ("trivy-results.sarif" )
103137 return True , "Trivy scan completed"
104-
138+
105139 except Exception as e :
106140 return False , f"Trivy scan failed: { e } "
107-
141+
108142 def run_hadolint (self ) -> Tuple [bool , str ]:
109143 """Run Hadolint Dockerfile security scan."""
110144 print ("Running Hadolint Dockerfile scan..." )
111-
145+
112146 try :
113- subprocess .run ([
114- "hadolint" , "Dockerfile" , "--format" , "sarif"
115- ], cwd = self .project_root ,
116- stdout = open (self .project_root / "hadolint-results.sarif" , "w" ),
117- check = False )
118-
147+ subprocess .run (
148+ ["hadolint" , "Dockerfile" , "--format" , "sarif" ],
149+ cwd = self .project_root ,
150+ stdout = open (self .project_root / "hadolint-results.sarif" , "w" ),
151+ check = False ,
152+ )
153+
119154 self .sarif_files .append ("hadolint-results.sarif" )
120155 return True , "Hadolint scan completed"
121-
156+
122157 except Exception as e :
123158 return False , f"Hadolint scan failed: { e } "
124-
159+
125160 def generate_sbom (self ) -> Tuple [bool , str ]:
126161 """Generate Software Bill of Materials."""
127162 print ("Generating SBOM files..." )
128-
163+
129164 try :
130165 # Python dependencies SBOM with pip-audit (only CycloneDX format is supported)
131- subprocess .run ([
132- "python" , "-m" , "pip_audit" , "--format=cyclonedx-json" ,
133- "--output=python-sbom-cyclonedx.json"
134- ], cwd = self .project_root , check = False )
135-
166+ subprocess .run (
167+ [
168+ "python" ,
169+ "-m" ,
170+ "pip_audit" ,
171+ "--format=cyclonedx-json" ,
172+ "--output=python-sbom-cyclonedx.json" ,
173+ ],
174+ cwd = self .project_root ,
175+ check = False ,
176+ )
177+
136178 # Check if Syft is available
137179 if subprocess .run (["which" , "syft" ], capture_output = True ).returncode == 0 :
138180 # Project SBOM with Syft
139- subprocess .run ([
140- "syft" , "." , "-o" , "spdx-json=project-sbom-spdx.json"
141- ], cwd = self .project_root , check = False )
142-
143- subprocess .run ([
144- "syft" , "." , "-o" , "cyclonedx-json=project-sbom-cyclonedx.json"
145- ], cwd = self .project_root , check = False )
181+ subprocess .run (
182+ ["syft" , "." , "-o" , "spdx-json=project-sbom-spdx.json" ],
183+ cwd = self .project_root ,
184+ check = False ,
185+ )
186+
187+ subprocess .run (
188+ ["syft" , "." , "-o" , "cyclonedx-json=project-sbom-cyclonedx.json" ],
189+ cwd = self .project_root ,
190+ check = False ,
191+ )
146192 else :
147193 print ("Syft not available - skipping project SBOM generation" )
148-
194+
149195 return True , "SBOM generation completed"
150-
196+
151197 except Exception as e :
152198 return False , f"SBOM generation failed: { e } "
153-
199+
154200 def generate_report (self ) -> str :
155201 """Generate comprehensive security report."""
156202 print ("Generating security report..." )
157-
203+
158204 report = {
159205 "scan_timestamp" : subprocess .check_output (["date" , "-u" ]).decode ().strip (),
160206 "project" : "Open Host Factory Plugin" ,
@@ -166,14 +212,14 @@ def generate_report(self) -> str:
166212 "Address high and critical severity vulnerabilities first" ,
167213 "Update dependencies with known vulnerabilities" ,
168214 "Review container base image for security updates" ,
169- "Implement security controls for identified issues"
170- ]
215+ "Implement security controls for identified issues" ,
216+ ],
171217 }
172-
218+
173219 # Write JSON report
174220 with open (self .project_root / "security-report.json" , "w" ) as f :
175221 json .dump (report , f , indent = 2 )
176-
222+
177223 # Write markdown summary
178224 md_report = f"""# Security Scan Report
179225
@@ -183,19 +229,19 @@ def generate_report(self) -> str:
183229## Scans Performed
184230
185231"""
186-
232+
187233 for scan , (success , message ) in self .results .items ():
188234 status = "PASS" if success else "FAIL"
189235 md_report += f"- **{ status } **: { scan } - { message } \n "
190-
236+
191237 md_report += f"""
192238## Generated Files
193239
194240### SARIF Files (for GitHub Security tab)
195241"""
196242 for sarif_file in self .sarif_files :
197243 md_report += f"- `{ sarif_file } `\n "
198-
244+
199245 md_report += """
200246### Report Files
201247- `security-report.json` - Detailed JSON report
@@ -221,59 +267,59 @@ def generate_report(self) -> str:
221267- **pip-audit**: Python package vulnerability scanner
222268- **Syft**: SBOM generator
223269"""
224-
270+
225271 with open (self .project_root / "security-report.md" , "w" ) as f :
226272 f .write (md_report )
227-
273+
228274 return "security-report.md"
229-
275+
230276 def run_all_scans (self , include_container : bool = True ) -> Dict [str , Tuple [bool , str ]]:
231277 """Run all security scans."""
232278 print ("Starting comprehensive security scan..." )
233-
279+
234280 # Core security scans
235281 self .results ["Bandit" ] = self .run_bandit ()
236282 self .results ["Safety" ] = self .run_safety ()
237283 self .results ["SBOM Generation" ] = self .generate_sbom ()
238-
284+
239285 # Container scans (optional)
240286 if include_container :
241287 self .results ["Trivy" ] = self .run_trivy ()
242288 self .results ["Hadolint" ] = self .run_hadolint ()
243-
289+
244290 # Generate report
245291 report_file = self .generate_report ()
246292 print (f"Security report generated: { report_file } " )
247-
293+
248294 return self .results
249295
250296
251297def main ():
252298 """Main entry point."""
253299 parser = argparse .ArgumentParser (description = "Comprehensive security scanner" )
254- parser .add_argument ("--no-container" , action = "store_true" ,
255- help = "Skip container security scans" )
256- parser . add_argument ( "--project-root" , type = Path , default = Path .cwd (),
257- help = "Project root directory" )
258-
300+ parser .add_argument ("--no-container" , action = "store_true" , help = "Skip container security scans" )
301+ parser . add_argument (
302+ "--project-root" , type = Path , default = Path .cwd (), help = "Project root directory"
303+ )
304+
259305 args = parser .parse_args ()
260-
306+
261307 scanner = SecurityScanner (args .project_root )
262308 results = scanner .run_all_scans (include_container = not args .no_container )
263-
309+
264310 # Print summary
265- print ("\n " + "=" * 60 )
311+ print ("\n " + "=" * 60 )
266312 print ("SECURITY SCAN SUMMARY" )
267- print ("=" * 60 )
268-
313+ print ("=" * 60 )
314+
269315 all_passed = True
270316 for scan , (success , message ) in results .items ():
271317 status = "PASS" if success else "FAIL"
272318 print (f"{ status } : { scan } - { message } " )
273319 if not success :
274320 all_passed = False
275-
276- print ("=" * 60 )
321+
322+ print ("=" * 60 )
277323 if all_passed :
278324 print ("All security scans completed successfully" )
279325 sys .exit (0 )
0 commit comments