feat(host-agent): migrate local console to Jinja2 templates with autoescape
Tests / Test failed: 4, passed: 744

Replace hand-written f-string + html.escape() rendering in the Host Agent
local console with a module-level Jinja2 Environment configured with
select_autoescape(["html","xml"]). XSS safety now holds by mechanism
rather than per-call discipline — every operator-controlled field
(device name, connection_info, task summary, etc.) is escaped by the
engine uniformly.

Eight templates under host_agent/web/templates/ replace the former
_chrome(), _CSS, escape(), and per-page _xxx_body() helpers: base.html
(header/nav/CSS + {% block body %}), login, dashboard (with the polling
<script> preserved byte-identically inside {% raw %}), devices, account,
history, tasks_list, and task_detail. The task-list and task-detail
templates — added by the just-landed task-execution-progress-visibility
change — were also migrated here rather than left in f-string form,
since this change removes the shared helpers they depended on.

URLs, auth/session/CSRF semantics, redirects, and /api/status JSON are
unchanged. 15 new template tests cover render-smoke, XSS probing, script
byte-identity, and no-autoescape-bypass guards. Tasks 8.1-8.6 (manual
browser verification) remain.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 13:05:19 +08:00
co-authored by Claude Opus 4.6
parent ec261d57c2
commit 8381b3068a
20 changed files with 1286 additions and 394 deletions
Generated
+14
View File
@@ -570,6 +570,7 @@ dependencies = [
{ name = "device-cloud-platform" },
{ name = "fastapi" },
{ name = "httpx" },
{ name = "jinja2" },
{ name = "uvicorn", extra = ["standard"] },
]
@@ -579,6 +580,7 @@ requires-dist = [
{ name = "device-cloud-platform", editable = "packages/cloud-platform" },
{ name = "fastapi", specifier = ">=0.115.0" },
{ name = "httpx", specifier = ">=0.27.0" },
{ name = "jinja2", specifier = ">=3.1" },
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.30.0" },
]
@@ -862,6 +864,18 @@ wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" },
]
[[package]]
name = "jinja2"
version = "3.1.6"
source = { registry = "https://mirrors.aliyun.com/pypi/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://mirrors.aliyun.com/pypi/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d" }
wheels = [
{ url = "https://mirrors.aliyun.com/pypi/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" },
]
[[package]]
name = "jiter"
version = "0.16.0"