You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/06/02 00:06:48 UTC
(superset) 01/01: fix(reports): sanitize error text in email notification template
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch fix/report-email-error-sanitization
in repository https://gitbox.apache.org/repos/asf/superset.git
commit b81beef6cb9a63fdb048453d46895dbf87c0696e
Author: Claude Code <no...@anthropic.com>
AuthorDate: Mon Jun 1 17:06:21 2026 -0700
fix(reports): sanitize error text in email notification template
_error_template interpolated the error message straight into the HTML email
body without sanitization, while the normal content path runs description and
embedded data through nh3.clean. The error text comes from exception messages
that can embed data-controlled content (e.g. crafted table/column names in a
database error), so HTML could be injected into notification emails.
Run the error text through nh3.clean (stripping all tags) before interpolation,
matching the existing sanitization posture. Add a unit test asserting injected
markup in the error text is stripped.
Co-Authored-By: Claude Opus 4.8 <no...@anthropic.com>
---
superset/reports/notifications/email.py | 8 ++++-
.../reports/notifications/email_tests.py | 35 ++++++++++++++++++++++
2 files changed, 42 insertions(+), 1 deletion(-)
diff --git a/superset/reports/notifications/email.py b/superset/reports/notifications/email.py
index d0f51c9c4fb..4c172afd791 100644
--- a/superset/reports/notifications/email.py
+++ b/superset/reports/notifications/email.py
@@ -100,13 +100,19 @@ class EmailNotification(BaseNotification): # pylint: disable=too-few-public-met
def _error_template(self, text: str) -> str:
call_to_action = self._get_call_to_action()
+ # The error text is derived from exception messages that can embed
+ # data-controlled content (e.g. crafted table/column names in a DB
+ # error). Strip all HTML before interpolating it into the email body,
+ # matching the sanitization applied to the normal content path.
+ # pylint: disable=no-member
+ safe_text = nh3.clean(text, tags=set(), attributes={})
return __(
"""
<p>Your report/alert was unable to be generated because of the following error: %(text)s</p>
<p>Please check your dashboard/chart for errors.</p>
<p><b><a href="%(url)s">%(call_to_action)s</a></b></p>
""", # noqa: E501
- text=text,
+ text=safe_text,
url=self._content.url,
call_to_action=call_to_action,
)
diff --git a/tests/unit_tests/reports/notifications/email_tests.py b/tests/unit_tests/reports/notifications/email_tests.py
index e2e0eeb2990..54fb2d4b30e 100644
--- a/tests/unit_tests/reports/notifications/email_tests.py
+++ b/tests/unit_tests/reports/notifications/email_tests.py
@@ -64,6 +64,41 @@ def test_render_description_with_html() -> None:
assert '<td><a href="http://www.example.com">333</a></td>' in email_body
+def test_error_template_sanitizes_html() -> None:
+ # `superset.models.helpers`, a dependency of following imports,
+ # requires app context
+ from superset.reports.models import ReportRecipients, ReportRecipientType
+ from superset.reports.notifications.base import NotificationContent
+ from superset.reports.notifications.email import EmailNotification
+
+ content = NotificationContent(
+ name="test alert",
+ text='DB error near "<img src=x onerror=alert(1)><script>alert(2)</script>"',
+ header_data={
+ "notification_format": "PNG",
+ "notification_type": "Alert",
+ "owners": [1],
+ "notification_source": None,
+ "chart_id": None,
+ "dashboard_id": None,
+ "slack_channels": None,
+ "execution_id": "test-execution-id",
+ },
+ )
+ email_body = (
+ EmailNotification(
+ recipient=ReportRecipients(type=ReportRecipientType.EMAIL), content=content
+ )
+ ._get_content()
+ .body
+ )
+
+ # Injected markup in the error text must be stripped, not rendered.
+ assert "<img" not in email_body
+ assert "<script>" not in email_body
+ assert "onerror=alert(1)" not in email_body
+
+
@with_feature_flags(DATE_FORMAT_IN_EMAIL_SUBJECT=True)
def test_email_subject_with_datetime() -> None:
# `superset.models.helpers`, a dependency of following imports,