@@ -383,6 +383,29 @@ def escape(s: str) -> str:
383383 <td style="padding:.6rem 1rem;border-bottom:1px solid #2a2a3e">{ sev_badge (worst )} </td>
384384</tr>"""
385385
386+ # Pre-compute HTML fragments to avoid nested f-strings (Python 3.11 compat)
387+ severity_cards = "" .join (
388+ '<div style="background:#1a1a2e;border-radius:8px;padding:1rem;text-align:center'
389+ ';border-top:3px solid ' + SEVERITY_COLORS [s ] + '">'
390+ '<div style="font-size:2rem;font-weight:800;color:' + SEVERITY_COLORS [s ] + '">'
391+ + str (sev .get (s , 0 )) +
392+ '</div><div style="font-size:.8rem;opacity:.7;text-transform:uppercase'
393+ ';letter-spacing:.05em">' + s + '</div></div>'
394+ for s in ["critical" , "high" , "medium" , "low" ]
395+ )
396+ owasp_section = (
397+ '<h2 style="margin-bottom:1rem;font-size:1.1rem">OWASP LLM Top 10 Coverage</h2>'
398+ '<div style="background:#1a1a2e;border-radius:8px;overflow:hidden;margin-bottom:2rem">'
399+ '<table style="width:100%;border-collapse:collapse">'
400+ '<thead><tr style="background:#12121e">'
401+ '<th style="padding:.6rem 1rem;text-align:left;font-size:.8rem;opacity:.6">OWASP REF</th>'
402+ '<th style="padding:.6rem 1rem;text-align:left;font-size:.8rem;opacity:.6">CATEGORIES</th>'
403+ '<th style="padding:.6rem 1rem;text-align:center;font-size:.8rem;opacity:.6">FINDINGS</th>'
404+ '<th style="padding:.6rem 1rem;text-align:left;font-size:.8rem;opacity:.6">WORST SEVERITY</th>'
405+ '</tr></thead><tbody>' + owasp_rows + '</tbody></table></div>'
406+ if owasp_rows else ""
407+ )
408+
386409 html = f"""<!DOCTYPE html>
387410<html lang="en">
388411<head>
@@ -412,10 +435,7 @@ def escape(s: str) -> str:
412435
413436 <!-- Metrics Cards -->
414437 <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:1rem;margin-bottom:2rem">
415- { '' .join (f"""<div style="background:#1a1a2e;border-radius:8px;padding:1rem;text-align:center;border-top:3px solid { SEVERITY_COLORS [s ]} ">
416- <div style="font-size:2rem;font-weight:800;color:{ SEVERITY_COLORS [s ]} ">{ sev .get (s ,0 )} </div>
417- <div style="font-size:.8rem;opacity:.7;text-transform:uppercase;letter-spacing:.05em">{ s } </div>
418- </div>""" for s in ["critical" ,"high" ,"medium" ,"low" ])}
438+ { severity_cards }
419439 <div style="background:#1a1a2e;border-radius:8px;padding:1rem;text-align:center;border-top:3px solid #818cf8">
420440 <div style="font-size:2rem;font-weight:800;color:#818cf8">{ summary ['total_payloads' ]} </div>
421441 <div style="font-size:.8rem;opacity:.7;text-transform:uppercase;letter-spacing:.05em">Total Payloads</div>
@@ -431,7 +451,7 @@ def escape(s: str) -> str:
431451 </div>
432452
433453 <!-- OWASP Table -->
434- { '<h2 style="margin-bottom:1rem;font-size:1.1rem">OWASP LLM Top 10 Coverage</h2><div style="background:#1a1a2e;border-radius:8px;overflow:hidden;margin-bottom:2rem"><table style="width:100%;border-collapse:collapse"><thead><tr style="background:#12121e"><th style="padding:.6rem 1rem;text-align:left;font-size:.8rem;opacity:.6">OWASP REF</th><th style="padding:.6rem 1rem;text-align:left;font-size:.8rem;opacity:.6">CATEGORIES</th><th style="padding:.6rem 1rem;text-align:center;font-size:.8rem;opacity:.6">FINDINGS</th><th style="padding:.6rem 1rem;text-align:left;font-size:.8rem;opacity:.6">WORST SEVERITY</th></tr></thead><tbody>' + owasp_rows + '</tbody></table></div>' if owasp_rows else '' }
454+ { owasp_section }
435455
436456 <!-- Findings -->
437457 <h2 style="margin-bottom:1rem;font-size:1.1rem">Findings</h2>
0 commit comments