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>
41 lines
1.5 KiB
HTML
41 lines
1.5 KiB
HTML
<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>{{ title }}</title>
|
|
<style>{% block styles %}body { font-family: system-ui, sans-serif; margin: 0; background: #f5f5f5; color: #222; }
|
|
header { background: #20303f; color: #fff; padding: 0.75rem 1.5rem; }
|
|
header nav { display: inline; margin-left: 1.5rem; }
|
|
header nav a, header nav form { display: inline-block; margin-right: 1rem; }
|
|
header a { color: #fff; text-decoration: none; }
|
|
header button { background: none; border: none; color: #fff; text-decoration: underline; cursor: pointer; padding: 0; font: inherit; }
|
|
main { padding: 1.5rem; max-width: 960px; margin: 0 auto; }
|
|
table { border-collapse: collapse; width: 100%; margin-bottom: 1rem; background: #fff; }
|
|
th, td { border: 1px solid #ccc; padding: 0.4rem 0.6rem; text-align: left; }
|
|
form.inline { display: inline; margin: 0; }
|
|
.error { color: #b00020; }
|
|
.notice { color: #1b5e20; }{% endblock %}</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<strong>Host Agent Console</strong>
|
|
{% block nav %}{% if session %}
|
|
<nav>
|
|
<a href="/">Status</a>
|
|
<a href="/devices">Devices</a>
|
|
<a href="/tasks">Tasks</a>
|
|
<a href="/account">Account</a>
|
|
<a href="/history">History</a>
|
|
<form class="inline" method="post" action="/logout">
|
|
<input type="hidden" name="csrf_token" value="{{ session.csrf_token }}">
|
|
<button type="submit">Logout</button>
|
|
</form>
|
|
</nav>
|
|
{% endif %}{% endblock %}
|
|
</header>
|
|
<main>
|
|
{% block body %}{% endblock %}
|
|
</main>
|
|
</body>
|
|
</html>
|