From a552db2599a870121c18973e0ed846ce9a1cd766 Mon Sep 17 00:00:00 2001 From: 0xmrma Date: Fri, 6 Mar 2026 08:45:14 +0200 Subject: [PATCH] Enable autoescaping in report templates to prevent HTML injection --- ax/utils/report/render.py | 7 +++- .../report/resources/simple_template.html | 2 +- .../resources/sufficient_statistic.html | 2 +- scripts/test_report_autoescape_smoke.py | 41 +++++++++++++++++++ 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100755 scripts/test_report_autoescape_smoke.py 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 "