Coverage for src / codeaudit / dashboard_reports.py: 0%

100 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-09 09:33 +0200

1""" 

2License GPLv3 or higher. 

3 

4(C) 2026 Created by Maikel Mardjan - https://nocomplexity.com/ 

5 

6This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 

7 

8This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 

9 

10You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. 

11 

12API functions: Used for dashboard reporting (Panel / WASM) and notebooks, or to build custom reports. 

13""" 

14 

15# import panel as pn 

16 

17SAST_REPORT_CSS = """ 

18<style> 

19.sast-report { 

20 font-family: Arial, sans-serif; 

21 max-width: 1200px; 

22 margin: 0 auto; /* centers it */ 

23} 

24 

25.sast-report table { 

26 border-collapse: collapse; 

27 width: 100%; /* ensures all tables take full container width */ 

28 max-width: 1200px; /* optional: caps table width */ 

29 margin-top: 10px; 

30} 

31 

32.sast-report th, .sast-report td { 

33 border: 1px solid #ddd; 

34 padding: 8px; 

35 vertical-align: top; 

36} 

37 

38.sast-report th { 

39 background-color: #E69F00; 

40 text-align: left; 

41} 

42 

43 

44/* Code blocks */ 

45pre { 

46 font-size: 1.25em;  

47 border-radius: 5px; 

48 overflow-x: auto; 

49 width: 100%; 

50 max-width: 800px; 

51} 

52 

53/* Code styling */ 

54.sast-report pre { 

55 margin: 0; 

56} 

57 

58.sast-report pre code.language-python { 

59 background-color: #2d2d2d; 

60 color: #f8f8f2; 

61 font-family: Consolas, Monaco, 'Courier New', monospace; 

62 font-size: 13px; 

63 line-height: 1.5; 

64 padding: 10px; 

65 border-radius: 6px; 

66 display: block; 

67 overflow-x: auto; 

68 white-space: pre; 

69} 

70 

71/* Severity colors */ 

72.severity-high { color: red; font-weight: bold; } 

73.severity-medium { color: orange; font-weight: bold; } 

74.severity-low { color: green; } 

75 

76details summary { 

77 cursor: pointer; 

78 font-weight: bold; 

79 margin-top: 5px; 

80} 

81</style> 

82""" 

83 

84 

85def _require_panel(): 

86 """Import the optional Panel dependency. 

87 

88 Returns: 

89 module: The imported panel module. 

90 

91 Raises: 

92 ImportError: If the panel package is not installed. The error message 

93 instructs the user to install the optional dependency. 

94 """ 

95 try: 

96 import panel as pn 

97 

98 return pn 

99 except ImportError: 

100 raise ImportError( 

101 "Optional dependency 'panel' not installed. " 

102 "Run: pip install your-package[panel]" 

103 ) 

104 

105 

106def create_statistics_overview(scanresult): 

107 """ 

108 Returns a Statistics Overview with Panel 

109 layout with HTML panes (4 per row). 

110 """ 

111 pn = _require_panel() # Panel module is needed for this function 

112 if not scanresult or not isinstance(scanresult, dict): 

113 return pn.pane.HTML("⚠️ No scan result") 

114 

115 statistics = scanresult.get("statistics_overview", {}) 

116 if not statistics: 

117 return pn.pane.HTML("⚠️ No statistics found") 

118 

119 title = scanresult.get("package_name", "Unknown") 

120 version = scanresult.get("package_release", "N/A") 

121 

122 # Style for statistic cards 

123 custom_style = { 

124 "background": "linear-gradient(135deg, #FFF3CC, #FFF9E6)", 

125 "padding": "16px", 

126 "min-width": "180px", 

127 "font-size": "16px", 

128 "color": "black", 

129 "border": "none", 

130 "border-left": "4px solid #E69F00", 

131 "border-radius": "10px", 

132 } 

133 

134 # Style for header (red accent) 

135 header_style = { 

136 "background": "#E8F5E9", 

137 "padding": "16px", 

138 "min-width": "180px", 

139 "font-size": "16px", 

140 "color": "black", 

141 "border": "none", 

142 "border-left": "4px solid #4CAF50", 

143 "border-radius": "10px", 

144 "margin-bottom": "12px", # Nice spacing below header 

145 } 

146 

147 # Create statistic panes 

148 panes = [] 

149 for key, value in statistics.items(): 

150 html_content = f""" 

151 <div style="text-align: center;"> 

152 {key}<br> 

153 <span style="font-size:22px; font-weight: bold;">{value}</span> 

154 </div> 

155 """ 

156 pane = pn.pane.HTML(html_content, styles=custom_style) 

157 panes.append(pane) 

158 

159 # Arrange panes: 4 per row 

160 rows = [] 

161 for i in range(0, len(panes), 4): 

162 row = pn.Row(*panes[i : i + 4], sizing_mode="stretch_width") 

163 rows.append(row) 

164 

165 # Styled Header (full width, matching row width) 

166 header_html = f""" 

167 <div> 

168 Package Overview</br></br> 

169 <b>{title}</b> — version <b>{version}</b> 

170 </div> 

171 """ 

172 header = pn.pane.HTML(header_html, styles=header_style) 

173 

174 # Final layout: Header + statistic rows 

175 return pn.Column(header, *rows, sizing_mode="stretch_width") 

176 

177 

178def report_sast_results(scanresult): 

179 """ 

180 Generates a complete HTML report of all SAST findings from the scan result. 

181 Each file's findings are shown in a collapsible <details> element. 

182 """ 

183 pn = _require_panel() # Panel module is needed for this function 

184 

185 # --- Input validation --- 

186 if not scanresult or not isinstance(scanresult, dict): 

187 return '<br><h2">⚠️ No scan result provided</h2>' 

188 

189 file_security_info = scanresult.get("file_security_info") 

190 if not isinstance(file_security_info, dict) or len(file_security_info) == 0: 

191 return '<br><h2">⚠️ No file security info found</h2>' 

192 

193 # Collect files that have SAST results 

194 files_with_findings = [] 

195 for file_id, file_info in file_security_info.items(): 

196 if isinstance(file_info, dict): 

197 sast_result = file_info.get("sast_result") 

198 if isinstance(sast_result, dict) and len(sast_result) > 0: 

199 files_with_findings.append(file_info) 

200 

201 if not files_with_findings: 

202 return '<br><h2">✅ No security weaknesses found</h2>' 

203 

204 total_number_of_files = scanresult["statistics_overview"]["Number_Of_Files"] 

205 # --- HTML REPORT --- 

206 html = SAST_REPORT_CSS + f""" 

207 <div class="sast-report"> 

208 <h2>Detailed Code Security Report</h2> 

209 <p><strong>Package:</strong> {scanresult.get("package_name", "N/A")}</p> 

210 <p><strong>version:</strong> {scanresult.get("package_release", "N/A")}</p> 

211 <p><strong>Total files with findings:</strong> {len(files_with_findings)} of {total_number_of_files} files in total</p> 

212 """ 

213 

214 for file_info in files_with_findings: 

215 filename = file_info.get("FileName", "Unknown File") 

216 sast_result = file_info.get("sast_result", {}) 

217 num_issues = len(sast_result) 

218 

219 html += f""" 

220 <p>⚠️ <b>{num_issues}</b> potential security issue{"s" if num_issues > 1 else ""}  

221 found in <b>{filename}</b></p> 

222 """ 

223 

224 html += "<details>" 

225 html += "<summary>View identified security weaknesses</summary>" 

226 

227 html += """ 

228 <table> 

229 <thead> 

230 <tr> 

231 <th>Line</th> 

232 <th>Validation</th> 

233 <th>Severity</th> 

234 <th>Info</th> 

235 <th>Code Snippet</th> 

236 </tr> 

237 </thead> 

238 <tbody> 

239 """ 

240 

241 sorted_findings = sorted( 

242 sast_result.values(), key=lambda x: int(x.get("line", 0)) 

243 ) 

244 

245 for finding in sorted_findings: 

246 line = finding.get("line", "—") 

247 validation = finding.get("validation", "—") 

248 severity = finding.get("severity", "—") 

249 info = finding.get("info", "—") 

250 code = finding.get("code", "") 

251 

252 html += f""" 

253 <tr> 

254 <td><strong>{line}</strong></td> 

255 <td><code>{validation}</code></td> 

256 <td><span class="severity-{severity}">{severity}</span></td> 

257 <td>{info}</td> 

258 <td>{code}</td> 

259 </tr> 

260 """ 

261 

262 html += "</tbody></table>" 

263 html += "</details><br>" 

264 

265 html += "</div>" 

266 RESULT_HTML_PANE = { 

267 "background": "#FFFFE0", 

268 "padding": "16px", 

269 "min-width": "180px", 

270 "font-size": "16px", 

271 "color": "black", 

272 "border": "none", 

273 "border-left": "4px solid #E69F00", 

274 "border-radius": "10px", 

275 } 

276 sast_result = pn.pane.HTML(html, styles=RESULT_HTML_PANE) 

277 return sast_result 

278 

279 

280def report_used_modules(scanresult): 

281 """reports used modules for a package""" 

282 pn = _require_panel() # Panel module is needed for this function 

283 # --- Input validation --- 

284 card1 = "" 

285 if not scanresult or not isinstance(scanresult, dict): 

286 return '<br><h2">⚠️ No scan result provided</h2>' 

287 modules_discovered = scanresult["module_overview"] 

288 core_modules = modules_discovered["core_modules"] 

289 external_modules = modules_discovered["imported_modules"] 

290 card1 += "<details>" 

291 card1 += "<summary><b>Used Python Standard libraries</b></summary>" 

292 

293 card1 += ( 

294 "<ul>\n" 

295 + "\n".join(f" <li>{module}</li>" for module in core_modules) 

296 + "\n</ul>" 

297 ) 

298 card1 += "</details>" 

299 

300 card2 = "<details>" 

301 card2 += "<summary><b>Imported libraries (modules)</b></summary>" 

302 card2 += ( 

303 "<ul>\n" 

304 + "\n".join(f" <li>{module}</li>" for module in external_modules) 

305 + "\n</ul>" 

306 ) 

307 card2 += "</details>" 

308 # Style for statistic cards 

309 custom_style = { 

310 "background": "linear-gradient(135deg, #FFF3CC, #FFF9E6)", 

311 "padding": "16px", 

312 "margin-bottom": "24px", 

313 "min-width": "180px", 

314 "font-size": "16px", 

315 "color": "black", 

316 "border": "none", 

317 "border-left": "4px solid #E69F00", 

318 "border-radius": "10px", 

319 } 

320 cardoutput_1 = pn.pane.HTML(card1, styles=custom_style) 

321 cardoutput_2 = pn.pane.HTML(card2, styles=custom_style) 

322 cards = pn.Row(cardoutput_1, cardoutput_2) 

323 return cards 

324 

325 

326def get_info_text(): 

327 """returns the info text styled 

328 for the sidebar 

329 """ 

330 pn = _require_panel() # Panel module is needed for this function 

331 custom_style = { 

332 "background": "linear-gradient(135deg, #F0FDF4, #ECFDF5)", # soft green tint 

333 "padding": "18px", 

334 "min-width": "200px", 

335 "font-size": "15px", 

336 "color": "#1F2937", # slightly richer dark text 

337 "border": "1px solid #D1FAE5", # subtle green border 

338 "border-left": "4px solid #10B981", # emerald accent 

339 "border-radius": "12px", 

340 "box-shadow": "0 2px 6px rgba(0, 0, 0, 0.05)", # soft depth 

341 } 

342 

343 infotext = pn.pane.HTML( 

344 """ 

345 <p><b>Python Code Audit</b> is a Static Application Security Testing (SAST) tool used to find security weaknesses in Python code.</p> 

346 <p>Use the CLI version for powerful command-line scanning with many more options.<p> 

347 <br><br>  

348 <a href="https://nocomplexity.com/documents/codeaudit/intro.html" 

349 target="_blank" 

350 style=" 

351 display: inline-flex; 

352 align-items: center; 

353 gap: 8px; 

354 padding: 10px 18px; 

355 background-color: #24292f; 

356 color: white; 

357 text-decoration: none; 

358 border-radius: 8px; 

359 font-weight: 600; 

360 font-size: 15px; 

361 transition: all 0.2s ease; 

362 width: 200px; 

363 ">Documentation 

364 </a> 

365 <br><br> 

366 <a href="https://github.com/nocomplexity/codeaudit" 

367 target="_blank" 

368 style=" 

369 display: inline-flex; 

370 align-items: center; 

371 gap: 8px; 

372 padding: 10px 18px; 

373 background-color: #24292f; 

374 color: white; 

375 text-decoration: none; 

376 border-radius: 8px; 

377 font-weight: 600; 

378 font-size: 15px; 

379 transition: all 0.2s ease; 

380 width: 200px; 

381 "> 

382 <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 24 24"> 

383 <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.66-1.455-3.66-1.455-.495-1.26-1.2-1.59-1.2-1.59-.975-.66.075-.645.075-.645 1.08.075 1.65 1.11 1.65 1.11 0.96 1.635 2.505 1.17 3.12.885.09-.69.375-1.17.675-1.44-2.55-.285-5.22-1.275-5.22-5.67 0-1.26.45-2.28 1.185-3.09-.12-.285-.51-1.425.105-2.97 0 0 .975-.315 3.195 1.185.93-.255 1.92-.375 2.91-.375s1.98.12 2.91.375c2.22-1.5 3.195-1.185 3.195-1.185.615 1.545.225 2.685.105 2.97.735.81 1.185 1.83 1.185 3.09 0 4.41-2.67 5.385-5.22 5.67.405.345.765 1.02.765 2.07 0 1.5-.015 2.715-.015 3.09 0 .315.225.69.825.57C20.565 21.795 24 17.31 24 12c0-6.63-5.37-12-12-12z"/> 

384 </svg> 

385 GitHub 

386 </a> 

387  

388 """, 

389 sizing_mode="stretch_width", 

390 stylesheets=[""" 

391 .bk-panel { 

392 background: transparent !important; 

393 } 

394 """], 

395 styles=custom_style, 

396 ) 

397 return infotext 

398 

399 

400def get_disclaimer_text(): 

401 """defines the sidebar disclaimer text""" 

402 pn = _require_panel() # Panel module is needed for this function 

403 disclaimer = ( 

404 "<br><b>Disclaimer:</b>This scan only evaluates Python files. " 

405 "Security weaknesses can also exist in other files used by a Python package.<br><br>" 

406 'This SAST tool <a href="https://github.com/nocomplexity/codeaudit" target="_blank">' 

407 "Python Code Audit</a> provides a powerful, automatic security analysis for Python source code. " 

408 "However, it's not a substitute for human review in combination with business knowledge. " 

409 "Undetected vulnerabilities may still exist." 

410 ) 

411 

412 disclaimer_text = pn.pane.HTML(disclaimer) 

413 return disclaimer_text