Skip to content

feat(alerts): add opt-in "Allow HTML content" for custom templates#7765

Open
Darian-Morrison wants to merge 3 commits into
getredash:masterfrom
TutoredByTeachers:feat/alert-allow-html-optin
Open

feat(alerts): add opt-in "Allow HTML content" for custom templates#7765
Darian-Morrison wants to merge 3 commits into
getredash:masterfrom
TutoredByTeachers:feat/alert-allow-html-optin

Conversation

@Darian-Morrison

@Darian-Morrison Darian-Morrison commented Jul 1, 2026

Copy link
Copy Markdown

What type of PR is this?

  • Feature

Description

In a custom alert subject/body, values interpolated from query results ({{QUERY_RESULT_VALUE}}, table rows, and other {{...}} variables) are HTML-escaped by default. This prevents dynamic injection of HTML content into Alert bodies.

This adds an "Allow HTML content" checkbox to the alert template editor (Inspired by the table visualization per-column toggle) that renders the custom template without escaping.

For our purposes, this gives Slack destined alerts mrkdwn embedding allowing dynamic user tagging and hyperlinking.

How is this tested?

  • Unit tests (pytest)
  • Manually

Added tests/models/test_alerts.py cases: escape-by-default, raw rendering when enabled
(subject + body), and that a direct render_template() call stays escaped. Manually
verified in the alert editor and against a rendered alert.

Related Tickets & Documents

N/A

Mobile & Desktop Screenshots/Recordings (if there are UI changes)

image image image

Review in cubic

Values interpolated into a custom alert subject/body (query results and other {{...}} variables) are HTML-escaped by default. That is correct for the HTML email body but turns links and markup from query results into literal text in other destinations. Add an "Allow HTML content" checkbox to the alert template editor, mirroring the table visualization's per-column toggle, that renders the custom template without escaping.

Escaping stays on by default, and the flag only affects the author's custom template -- the system default mail body is always escaped.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="redash/models/__init__.py">

<violation number="1" location="redash/models/__init__.py:1084">
P1: When `allow_html` is enabled, `render_template` switches the entire template from the escaping renderer to raw Mustache for all variables—including `QUERY_RESULT_VALUE`, `QUERY_RESULT_ROWS`, and other query-derived context—without visible sanitization or destination-specific controls. Since query results may contain attacker-controlled data, an alert owner opting in to HTML for Slack `mrkdwn` could inadvertently inject executable markup into email or web UI notifications that other subscribers receive. Consider scoping the raw rendering to intended variables or adding downstream sanitization so the opt-in does not become a cross-channel XSS vector.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread redash/models/__init__.py
"QUERY_RESULT_TABLE": result_table,
}
return mustache_render_escape(template, context)
render = mustache_render_escape if escape else mustache_render

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When allow_html is enabled, render_template switches the entire template from the escaping renderer to raw Mustache for all variables—including QUERY_RESULT_VALUE, QUERY_RESULT_ROWS, and other query-derived context—without visible sanitization or destination-specific controls. Since query results may contain attacker-controlled data, an alert owner opting in to HTML for Slack mrkdwn could inadvertently inject executable markup into email or web UI notifications that other subscribers receive. Consider scoping the raw rendering to intended variables or adding downstream sanitization so the opt-in does not become a cross-channel XSS vector.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At redash/models/__init__.py, line 1084:

<comment>When `allow_html` is enabled, `render_template` switches the entire template from the escaping renderer to raw Mustache for all variables—including `QUERY_RESULT_VALUE`, `QUERY_RESULT_ROWS`, and other query-derived context—without visible sanitization or destination-specific controls. Since query results may contain attacker-controlled data, an alert owner opting in to HTML for Slack `mrkdwn` could inadvertently inject executable markup into email or web UI notifications that other subscribers receive. Consider scoping the raw rendering to intended variables or adding downstream sanitization so the opt-in does not become a cross-channel XSS vector.</comment>

<file context>
@@ -1081,17 +1081,18 @@ def render_template(self, template):
             "QUERY_RESULT_TABLE": result_table,
         }
-        return mustache_render_escape(template, context)
+        render = mustache_render_escape if escape else mustache_render
+        return render(template, context)
 
</file context>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why the option is opt-in and has a warning tooltip.

Comment thread redash/models/__init__.py Outdated
Darian-Morrison and others added 2 commits July 1, 2026 13:32
options is stored as raw JSONB, so a truthy non-boolean value (e.g. the string "false") could otherwise disable escaping. Gate on `is True` so only an explicit boolean opt-in renders the custom template unescaped.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant