diff --git a/valkyrie/core/formatters/__init__.py b/valkyrie/core/formatters/__init__.py new file mode 100644 index 0000000..d850010 --- /dev/null +++ b/valkyrie/core/formatters/__init__.py @@ -0,0 +1,7 @@ +from .base import ResultFormatter +from .sarif import SARIFFormatter + +__all__ = [ + ResultFormatter, + SARIFFormatter +] \ No newline at end of file diff --git a/valkyrie/core/formatters/html.py b/valkyrie/core/formatters/html.py new file mode 100644 index 0000000..65daa74 --- /dev/null +++ b/valkyrie/core/formatters/html.py @@ -0,0 +1,134 @@ +""" +Valkyrie - HTML Scan Result Formatter +""" + +from valkyrie.core.types import ( + SeverityLevel, ScanResult, ScanStatus +) + +from .base import ResultFormatter + + +#### +## HTML SCAN RESUT FORMATTER +##### +class HTMLFormatter(ResultFormatter): + """HTML report formatter""" + + def format(self, result: ScanResult) -> str: + """Format results as HTML report""" + + html = f""" + + + + + + Valkyrie Security Scan Report + + + +
+
+

🛡️ Valkyrie Security Scan Report

+

Scan completed on {result.timestamp.strftime('%Y-%m-%d %H:%M:%S')}

+

Duration: {result.scan_duration:.2f}s | Files scanned: {len(result.scanned_files)}

+
+ +
+ {self._generate_summary_html(result)} + {self._generate_findings_html(result)} +
+
+ + + """ + return html + + def _generate_summary_html(self, result: ScanResult) -> str: + """Generate summary section HTML""" + + if result.status == ScanStatus.FAILED: + return '

❌ Scan Failed

Check logs for details

' + + return f""" +
+
+

{len(result.findings)}

+

Total Issues

+
+
+

{result.critical_count}

+

Critical

+
+
+

{result.high_count}

+

High

+
+
+

{sum(1 for f in result.findings if f.severity == SeverityLevel.MEDIUM)}

+

Medium

+
+
+

{sum(1 for f in result.findings if f.severity == SeverityLevel.LOW)}

+

Low

+
+
+ """ + + def _generate_findings_html(self, result: ScanResult) -> str: + """Generate findings section HTML""" + + if not result.findings: + return '

✅ No security issues found!

All scanned files are secure.

' + + html = "

Security Findings

" + + # Sort findings by severity + sorted_findings = sorted(result.findings, key=lambda f: [ + SeverityLevel.CRITICAL, SeverityLevel.HIGH, SeverityLevel.MEDIUM, + SeverityLevel.LOW, SeverityLevel.INFO + ].index(f.severity)) + + for finding in sorted_findings: + severity_class = f"severity-{finding.severity.value}" + html += f""" +
+
+ {finding.severity.value} + {finding.title} +
+ {finding.location.file_path}:{finding.location.line_number} +
+
+
+

{finding.description}

+ {f'

Remediation: {finding.remediation_advice}

' if finding.remediation_advice else ''} +

Rule ID: {finding.rule_id} | Confidence: {finding.confidence:.1%}

+
+
+ """ + + return html