From 7cbc56adb3576f0bb099405c5b43b86b3dcbec5f Mon Sep 17 00:00:00 2001 From: pedrooot Date: Tue, 13 Oct 2026 17:22:55 +0200 Subject: [PATCH] fix(html): escape the compliance column --- .../html-compliance-escape.security.md | 1 + prowler/lib/outputs/html/html.py | 2 +- tests/lib/outputs/html/html_test.py | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 prowler/changelog.d/html-compliance-escape.security.md diff --git a/prowler/changelog.d/html-compliance-escape.security.md b/prowler/changelog.d/html-compliance-escape.security.md new file mode 100644 index 0000000000..98b4b12a04 --- /dev/null +++ b/prowler/changelog.d/html-compliance-escape.security.md @@ -0,0 +1 @@ +HTML report compliance column is now escaped, so framework names and requirement IDs containing markup are shown as text instead of being rendered by the browser diff --git a/prowler/lib/outputs/html/html.py b/prowler/lib/outputs/html/html.py index 60428bede7..da3faa8b1c 100644 --- a/prowler/lib/outputs/html/html.py +++ b/prowler/lib/outputs/html/html.py @@ -116,7 +116,7 @@ class HTML(Output): {str(escape(finding.status_extended)).replace("_", "_")}

{HTML.process_markdown(str(escape(finding.metadata.Risk)))}

{HTML.process_markdown(str(escape(finding.metadata.Remediation.Recommendation.Text)))}

-

{parse_html_string(unroll_dict(finding.compliance, separator=": "))}

+

{parse_html_string(str(escape(unroll_dict(finding.compliance, separator=": "))))}

""") except Exception as error: diff --git a/tests/lib/outputs/html/html_test.py b/tests/lib/outputs/html/html_test.py index 1590d29742..6c39e43249 100644 --- a/tests/lib/outputs/html/html_test.py +++ b/tests/lib/outputs/html/html_test.py @@ -910,6 +910,23 @@ class TestHTML: assert "•key&<>'"<img" in output_data assert "=value&<>'"<img" in output_data + def test_transform_escapes_compliance(self): + xss_payload = '' + findings = [ + generate_finding_output( + compliance={f"Framework{xss_payload}-1.0": [xss_payload, "1.1"]}, + ) + ] + + output_data = HTML(findings).data[0] + + assert xss_payload not in output_data + assert ( + "•Framework<img src=x " + "onerror="window.PROWLER_COMPLIANCE_XSS=1">-1.0: " + "<img src=x onerror="window.PROWLER_COMPLIANCE_XSS=1">, 1.1" + ) in output_data + def test_transform_escapes_metadata_fields(self): finding = generate_finding_output() finding.metadata.Severity = MagicMock(value='')