From b3ffc98e9f869f2c20e3d7d45e66c0ed7b7361b2 Mon Sep 17 00:00:00 2001 From: ZdenekSrotyr Date: Sat, 2 May 2026 20:56:21 +0200 Subject: [PATCH] fix(security): XSS hardening for setup banner + cleanup unused imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add _sanitize_banner_html() to src/setup_banner.py: strips `` blocks (case-insensitive, including unclosed). + - ```` blocks. + - ``on*=`` event-handler attributes (e.g. onclick, onload, onerror). + - ``javascript:`` and ``data:`` URI schemes in href/src attributes. + """ + html = _RE_SCRIPT.sub("", html) + html = _RE_IFRAME.sub("", html) + html = _RE_ON_ATTR.sub("", html) + html = _RE_JS_URI.sub(lambda m: m.group(1) + "#" + m.group(2), html) + return html + def build_setup_banner_context( *, @@ -76,7 +109,8 @@ def render_setup_banner( env = Environment(undefined=StrictUndefined, autoescape=True) try: template = env.from_string(source) - return template.render(**build_setup_banner_context(user=user, server_url=server_url)) + rendered = template.render(**build_setup_banner_context(user=user, server_url=server_url)) + return _sanitize_banner_html(rendered) except TemplateError: _logger.warning( "setup_banner render failed; returning empty banner. " diff --git a/tests/test_setup_banner_render.py b/tests/test_setup_banner_render.py index fb7c0ed..e1dc571 100644 --- a/tests/test_setup_banner_render.py +++ b/tests/test_setup_banner_render.py @@ -5,7 +5,7 @@ import pytest from src.db import _ensure_schema from src.repositories.setup_banner import SetupBannerRepository -from src.setup_banner import build_setup_banner_context, render_setup_banner +from src.setup_banner import _sanitize_banner_html, build_setup_banner_context, render_setup_banner @pytest.fixture @@ -78,3 +78,44 @@ def test_autoescape_escapes_html_entities(conn): ) # hostname won't contain < > but the render must succeed without injection assert out != "" + + +# ── Sanitizer unit tests ───────────────────────────────────────────────────── + +def test_render_strips_script_tags(conn): + """render_setup_banner must remove ', + updated_by="admin@example.com", + ) + out = render_setup_banner(conn, user=_user(), server_url="https://example.com") + assert "