Files
q792602257andClaude Opus 4.6 8381b3068a
Tests / Test failed: 4, passed: 744
feat(host-agent): migrate local console to Jinja2 templates with autoescape
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>
2026-07-14 13:05:19 +08:00

50 lines
6.2 KiB
Markdown

## 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 `.html` templates, and the page handler does not construct any HTML fragment via f-string, string concatenation, or `str.format`
#### Scenario: A new page added later inherits autoescape without extra wiring
- **WHEN** a future change adds a new `.html` template 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 `HTMLResponse` whose `content` is the string returned by the template engine's `render(...)` 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 `&lt;script&gt;alert(1)&lt;/script&gt;` (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_info` JSON 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