From fef9f3a2b2b61a0d6d85c2c2a167891d07501dd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Wr=C3=B3bel?= Date: Fri, 3 Jul 2026 15:24:56 +0200 Subject: [PATCH] Escape spec_path in HTML report to prevent XSS vulnerabilities. Add test to ensure proper HTML escaping of user-supplied spec_path. --- backend/app/core/notifications.py | 2 +- backend/test/api/test_email_notifications.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/backend/app/core/notifications.py b/backend/app/core/notifications.py index cd9e4de..4ca40ff 100644 --- a/backend/app/core/notifications.py +++ b/backend/app/core/notifications.py @@ -1285,7 +1285,7 @@ def render_generation_session_report_html( # Summary section html_parts.append('
') html_parts.append('

Summary

') - html_parts.append(f'
Specification: {spec_path}
') + html_parts.append(f'
Specification: {html.escape(spec_path)}
') html_parts.append(f'
Run ID: {generation_id}
') html_parts.append( f'
{variance.label}: ' diff --git a/backend/test/api/test_email_notifications.py b/backend/test/api/test_email_notifications.py index f522c34..d6e2acb 100644 --- a/backend/test/api/test_email_notifications.py +++ b/backend/test/api/test_email_notifications.py @@ -598,6 +598,26 @@ def test_html_builder_is_the_single_source_used_by_email( assert direct_html == via_notifier_html assert direct_plain == via_notifier_plain + def test_spec_path_is_html_escaped( + self, mock_db, sample_workspace_docs, sample_estimation_result + ): + """spec_path is a free-form, caller-suppliable string — it must not be + interpolated into the HTML report unescaped (XSS).""" + from app.core.notifications import render_generation_session_report_html + + workspace_ids, _ = sample_workspace_docs + + html_content, _ = render_generation_session_report_html( + generation_id="est-test-123", + workspace_ids=workspace_ids, + result=sample_estimation_result, + spec_path="", + db=mock_db, + ) + + assert "" not in html_content + assert "<script>alert(1)</script>" in html_content + class TestMultiWorkspaceResultSerialization: """Stored Firestore result shape for email resend / P10Y metadata."""