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>
6.2 KiB
ADDED Requirements
Requirement: Console HTML pages SHALL be rendered through a template engine with automatic HTML escaping enabled
The Host Agent local console SHALL render every HTML page through a single, process-wide template engine configured so that values interpolated into templates whose names end in .html are HTML-escaped by the engine before being written into the response, independent of whether the calling code remembers to escape them. The template engine and its autoescape configuration SHALL be constructed once at module import time of host_agent/web/app.py and reused by every page handler; pages SHALL NOT bypass the engine by constructing HTML through string concatenation, f-string interpolation, or any other path that skips autoescape.
Scenario: All current pages go through the autoescaping engine
- WHEN the Host Agent renders any of its login, status dashboard, devices, account, or history pages
- THEN the rendered HTML is produced by the shared template engine with autoescape enabled for
.htmltemplates, and the page handler does not construct any HTML fragment via f-string, string concatenation, orstr.format
Scenario: A new page added later inherits autoescape without extra wiring
- WHEN a future change adds a new
.htmltemplate under the Host Agent console's templates directory and a route handler renders it through the shared template engine - THEN that new page's interpolated values are HTML-escaped by the engine without the new change having to reconfigure autoescape or wrap any value in an escaping helper
Scenario: Handler code returns a plain HTMLResponse built from the engine's output
- WHEN a console route handler produces its response
- THEN the response is an
HTMLResponsewhosecontentis the string returned by the template engine'srender(...)call, and the handler does not post-process that string in a way that could re-introduce unescaped markup
Requirement: Operator- and externally-influenced values rendered into console pages SHALL be HTML-escaped
Any value that originates from operator input (e.g. device name, driver type, connection info JSON, account username), from the control plane (e.g. host policy fields, assignment summaries), or from task execution (e.g. step summaries, scene/element descriptions) SHALL be HTML-escaped by the template engine when interpolated into a console page, so that the rendered output contains no executable <script> markup, event-handler attributes, or other HTML capable of executing in the browser session of an authenticated console operator.
Scenario: A device name containing an XSS probe is rendered escaped
- WHEN an operator registers a local device whose name contains the string
<script>alert(1)</script>and the operator subsequently loads the devices page or the status dashboard - THEN the rendered HTML contains the literal text
<script>alert(1)</script>(or an equivalent escaped form) in the position where the device name is interpolated, and contains no<script>element corresponding to the device name
Scenario: A driver connection_info JSON containing an XSS probe is rendered escaped
- WHEN a device's
connection_infoJSON value contains a string with the characters<,>,", or'in any position, and the devices page renders that value (for example inside a<textarea>edit field) - THEN the rendered HTML escapes those characters so the value round-trips back to the original JSON when the form is submitted unchanged, and the page contains no executable markup introduced by the connection_info value
Scenario: An assignment or step summary containing an XSS probe is rendered escaped
- WHEN a task summary, step status, scene description, or any other externally-influenced string containing
<script>markup is rendered on any console page (including any future task list, task detail, or timeline page added by a later change) - THEN the rendered HTML escapes the markup and contains no executable
<script>element corresponding to that string
Requirement: Disabling autoescape for a console template SHALL require an explicit, in-template opt-in
The console's template engine SHALL NOT be configured with autoescape disabled globally. A template that needs to interpolate trusted, pre-built HTML (for example a fragment assembled by Python code from already-escaped parts) SHALL opt out of autoescape only for the specific region and only by an explicit in-template construct (such as Jinja2's {% autoescape false %}...{% endautoescape %} block or the | safe filter applied to a specific expression). No current console page requires such an opt-out.
Scenario: Global autoescape is on
- WHEN the console's template engine is constructed at module import time
- THEN its configuration enables autoescape for every template whose name ends in
.html, and no process-wide setting, environment variable, or configuration field disables that behavior
Scenario: A future page that needs trusted-HTML interpolation scopes the opt-out locally
- WHEN a later change adds a console template that interpolates a fragment the change has already assembled and escaped on the Python side, and that change wishes to avoid double-escaping
- THEN the opt-out is expressed as an explicit, template-local construct around the specific interpolation, and does not disable autoescape for the rest of the template or for any other template
Requirement: The console's template-rendering configuration SHALL be observable in tests
The console's module-level template engine SHALL be exposed in a way that lets automated tests render any console template with a caller-supplied context and assert on the resulting HTML, so that the autoescape behavior of every current and future page can be verified by a regression test without spinning up the full Host Agent HTTP server.
Scenario: A test renders a template with a malicious context
- WHEN an automated test calls the console's template engine with a template name and a context that includes a field whose value is
<script>alert(1)</script> - THEN the test receives the rendered HTML as a string and can assert that the XSS probe has been HTML-escaped in the output