Enable Jinja2 autoescaping (#200)

- Enable Jinja2 autoescape by default in the template environment.
- Use json.dumps to safely inject sso_name into JavaScript context.
- Fix linting issue (line too long) in injected_auth_page.py.
- Update tests to verify escaping and safe injection.

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: werdnum <271070+werdnum@users.noreply.github.com>
This commit is contained in:
Andrew Garrett
2026-02-06 19:07:54 +11:00
committed by GitHub
parent eaed91016a
commit b2d07c28f0
4 changed files with 14 additions and 8 deletions

View File

@@ -1,5 +1,6 @@
"""Injected authorization page, replacing the original""" """Injected authorization page, replacing the original"""
import json
import logging import logging
from functools import partial from functools import partial
from homeassistant.components.http import HomeAssistantView, StaticPathConfig from homeassistant.components.http import HomeAssistantView, StaticPathConfig
@@ -61,12 +62,9 @@ async def frontend_injection(hass: HomeAssistant, sso_name: str) -> None:
frontend_code = await read_file(frontend_path) frontend_code = await read_file(frontend_path)
# Inject JS and register that route # Inject JS and register that route
frontend_code = frontend_code.replace( injection_js = "<script src='/auth/oidc/static/injection.js?v=3'></script>"
"</body>", sso_name_js = f"<script>window.sso_name = {json.dumps(sso_name)};</script>"
"<script src='/auth/oidc/static/injection.js?v=3'></script><script>window.sso_name = '" frontend_code = frontend_code.replace("</body>", f"{injection_js}{sso_name_js}</body>")
+ sso_name
+ "';</script></body>",
)
await hass.http.async_register_static_paths( await hass.http.async_register_static_paths(
[ [

View File

@@ -54,7 +54,9 @@ class AsyncTemplateRenderer:
if template_name not in templates: if template_name not in templates:
raise ValueError(f"Template '{template_name}' not found.") raise ValueError(f"Template '{template_name}' not found.")
env = Environment(loader=DictLoader(templates), enable_async=True) env = Environment(
loader=DictLoader(templates), enable_async=True, autoescape=True
)
template = env.get_template(template_name) template = env.get_template(template_name)
# Render template # Render template

View File

@@ -149,3 +149,4 @@ async def test_frontend_injection(hass: HomeAssistant, hass_client):
text = await resp.text() text = await resp.text()
assert "<script src='/auth/oidc/static/injection.js" in text assert "<script src='/auth/oidc/static/injection.js" in text
assert "window.sso_name = \"OpenID Connect (SSO)\";" in text

View File

@@ -15,8 +15,13 @@ async def test_real_template_render():
"""Test that view template can render an real existing template.""" """Test that view template can render an real existing template."""
renderer = AsyncTemplateRenderer() renderer = AsyncTemplateRenderer()
rendered = await renderer.render_template("welcome.html") await renderer.fetch_templates()
rendered = await renderer.render_template(
"welcome.html", name="<script>alert(1)</script>"
)
assert "<!DOCTYPE html>" in rendered assert "<!DOCTYPE html>" in rendered
assert "&lt;script&gt;alert(1)&lt;/script&gt;" in rendered
assert "<script>alert(1)</script>" not in rendered
@pytest.mark.asyncio @pytest.mark.asyncio