diff --git a/ax/utils/report/render.py b/ax/utils/report/render.py
index 108286891d8..dc308ec12c6 100644
--- a/ax/utils/report/render.py
+++ b/ax/utils/report/render.py
@@ -10,7 +10,7 @@
import pkgutil
from ax.plot.render import _js_requires, _load_css_resource as _load_plot_css_resource
-from jinja2 import Environment, FunctionLoader
+from jinja2 import Environment, FunctionLoader, select_autoescape
REPORT_MODULE_NAME = "ax.utils.report"
@@ -142,4 +142,7 @@ def _load_html_template(name: str) -> str:
def _get_jinja_environment() -> Environment:
- return Environment(loader=FunctionLoader(_load_html_template))
+ return Environment(
+ loader=FunctionLoader(_load_html_template),
+ autoescape=select_autoescape(["html", "xml"]),
+ )
diff --git a/ax/utils/report/resources/simple_template.html b/ax/utils/report/resources/simple_template.html
index 7d17e409dcb..b2ac77779ce 100644
--- a/ax/utils/report/resources/simple_template.html
+++ b/ax/utils/report/resources/simple_template.html
@@ -4,6 +4,6 @@
{% extends "base_template.html" %}
{% block content %}
{% for element in html_elements %}
-{{element}}
+{{element | safe }}
{% endfor %}
{% endblock %}
diff --git a/ax/utils/report/resources/sufficient_statistic.html b/ax/utils/report/resources/sufficient_statistic.html
index c81fb943814..845bd6b660c 100644
--- a/ax/utils/report/resources/sufficient_statistic.html
+++ b/ax/utils/report/resources/sufficient_statistic.html
@@ -16,7 +16,7 @@
{{cell.caption}}
- {{cell.html}}
+ {{ cell.html | safe }}
{% endif %}
{% endfor %}
diff --git a/scripts/test_report_autoescape_smoke.py b/scripts/test_report_autoescape_smoke.py
new file mode 100755
index 00000000000..dfed939e5b6
--- /dev/null
+++ b/scripts/test_report_autoescape_smoke.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+import importlib.util
+import sys
+import types
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1] # repo root
+REPORT_RES = ROOT / "ax" / "utils" / "report" / "resources"
+
+# ---- Stub ax.plot.render so render.py can import without importing full ax package ----
+ax_mod = types.ModuleType("ax")
+ax_plot_mod = types.ModuleType("ax.plot")
+ax_plot_render_mod = types.ModuleType("ax.plot.render")
+
+ax_plot_render_mod._js_requires = lambda *a, **k: ""
+ax_plot_render_mod._load_css_resource = lambda *a, **k: ""
+
+sys.modules["ax"] = ax_mod
+sys.modules["ax.plot"] = ax_plot_mod
+sys.modules["ax.plot.render"] = ax_plot_render_mod
+
+# ---- Load ax/utils/report/render.py by file path ----
+render_path = (ROOT / "ax" / "utils" / "report" / "render.py").resolve()
+spec = importlib.util.spec_from_file_location("ax_utils_report_render", render_path)
+assert spec and spec.loader, "Failed to load module spec"
+mod = importlib.util.module_from_spec(spec)
+spec.loader.exec_module(mod)
+
+# ---- Monkeypatch ALL pkgutil-based loaders so we don't need package resources ----
+mod._load_css_resource = lambda: ""
+mod._load_plot_css_resource = lambda: ""
+mod._load_html_template = lambda name: (REPORT_RES / name).read_text(encoding="utf-8")
+
+# ---- Now test escaping ----
+payload = "
"
+html = mod.render_report_elements(payload, [mod.p_html("hello")])
+
+assert "![]()