Skip to content

security: explicit verify=True + remove manual Markup() in settings render (M1, M3)#54

Merged
lewiswigmore merged 1 commit into
mainfrom
security/m-tier-hardening
May 29, 2026
Merged

security: explicit verify=True + remove manual Markup() in settings render (M1, M3)#54
lewiswigmore merged 1 commit into
mainfrom
security/m-tier-hardening

Conversation

@sebastiondev
Copy link
Copy Markdown
Collaborator

Summary

Closes M1 and M3 from the security review (issue #51). Pure defense-in-depth — no exploit chain is unblocked, but two foot-guns are eliminated.

M1 — explicit verify=True

Every httpx.Client / AsyncClient constructor now passes verify=True explicitly:

  • dictate/cleanup.py (×3)
  • dictate/health.py
  • dictate/vad.py
  • dictate/webui/routes.py

httpx already defaults to verify=True, so behaviour is unchanged. The value is making the verification policy visible at every call site so a future verify=False slip becomes obvious in review rather than silent.

M3 — remove manual Markup() in YAML highlighter

webui/routes.py:_highlight_yaml previously built HTML by f-string concat through markupsafe.escape(), wrapping the result in Markup() and carrying a # nosec B704. Correct, but fragile to future edits.

This change:

  1. Renames the helper to _tokenize_yaml and returns list[list[tuple[str, str]]] — pure data, no HTML.
  2. Updates settings.html to render those tokens via Jinja with autoescape on ({{ text }}).
  3. Removes the markupsafe import entirely. No Markup() construction left in the file.

Net effect: same rendered output (yaml-key + yaml-punctuation spans), zero raw-HTML construction in Python, no nosec to maintain.

Tests

  • tests/test_cleanup_circuit.py — updated FakeAsyncClient.__init__ to accept the new verify kwarg.
  • Existing test_settings_returns_html_and_resolved_path covers the new template path.
  • Manual smoke test of GET /settings: returns 200, contains both <span class="yaml-key"> and <span class="yaml-punctuation"> spans, no leftover Jinja markers.
  • pytest: 322 passed.
  • ruff check + ruff format --check: clean (some pre-existing format drift in routes.py was tidied incidentally by ruff format).

What's left from issue #51

  • H2 — pin/bundle silero-vad model (touches release pipeline, larger scope).
  • M2 — Pydantic schema for config/*.yaml (touches every config callsite, larger refactor).

…ender (M1, M3)

M1: pass verify=True explicitly to every httpx.Client / AsyncClient
constructor (cleanup, health, vad, webui dashboard probe). httpx defaults
to verify=True today, but stating it at every site prevents future
regression — anyone disabling TLS verification has to type it.

M3: replace the manual Markup() / escape() YAML highlighter in
webui/routes.py with a token list rendered by Jinja under autoescape.
The previous code was correct (every interpolation passed through
escape()) but carried a #nosec B704 and was a refactor-foot-gun.
Now there is no Markup() construction in the routes module at all;
the template iterates tokens and Jinja's HTML autoescape does the work.

Adjusted the FakeAsyncClient in test_cleanup_circuit.py to accept the
new verify kwarg. Full suite: 322 passed. Smoke test of /settings
confirms YAML renders with yaml-key / yaml-punctuation spans intact
and no unescaped output.

Tracks M1 + M3 in issue #51.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sebastionai
Copy link
Copy Markdown

sebastionai Bot commented May 29, 2026

Pre-merge checks · ✅ 2 · ⚠ 0 · ❌ 0 · ⏭ 1
Check Status Reason
PR title The title clearly describes the two specific security changes being made.
Description The body thoroughly explains what was changed and why for both M1 and M3.
Linked issue No linked issues were provided.

@sebastionai
Copy link
Copy Markdown

sebastionai Bot commented May 29, 2026

Walkthrough

Adds explicit verify=True to all httpx client call sites as defence in depth and removes manual Markup() HTML construction from the settings YAML highlighter in favour of pure data tokens rendered by Jinja with autoescape. Includes a minor test fix and incidental formatting tidies from ruff.

Changes

File Summary
TLS verification dictate/cleanup.py, dictate/health.py, dictate/vad.py Adds explicit verify=True to every httpx.Client and AsyncClient constructor call.
Template security dictate/webui/routes.py, dictate/webui/templates/settings.html Replaces manual Markup() HTML construction with a pure-data tokeniser and Jinja template rendering, removing the markupsafe dependency from routes.
Code formatting dictate/webui/routes.py Applies ruff formatting fixes to line wrapping and ternary expressions.
Tests tests/test_cleanup_circuit.py Updates FakeAsyncClient to accept the new verify kwarg.

🎯 Effort: 2 (Simple) · ⏱ ~12 minutes

Generated by Sebastion AI · docs

Copy link
Copy Markdown

@sebastionai sebastionai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Sebastion AI security review

1 finding on this PR. 🔸 1 medium

Sources: 1 LLM

Inline comments are posted on changed lines below.

Other findings (in scanned files but outside this diff)

  • MEDIUM template-injection-xss · CWE-79 — dictate/webui/templates/settings.html:16 — The variable load_error is rendered with Jinja2's default {{ }} which auto-escapes in most configurations, but if auto-escaping is disabled or the value contains trusted HTML markup, an attacker-controlled config error message could inject arbitrary HTML/JS. Verify Jinja2 Environment has autoescape=True.
      Fix: Ensure the Jinja2 Environment is created with autoescape=True or use {{ load_error|e }} explicitly.

Audited by Sebastion AI · docs · install on more repos

Prompt for all review comments with AI agents

In dictate/webui/templates/settings.html:

  • Line 16: In dictate/webui/templates/settings.html at line 16, the variable load_error is rendered with Jinja2's default {{ }} which auto-escapes in most configurations, but if auto-escaping is disabled or the value contains trusted HTML markup, an attacker-controlled config error message could inject arbitrary HTML/JS. Verify Jinja2 Environment has autoescape=True. Fix: Ensure the Jinja2 Environment is created with autoescape=True or use {{ load_error|e }} explicitly. Context: rule template-injection-xss and CWE CWE-79.

Copy link
Copy Markdown
Owner

@lewiswigmore lewiswigmore left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Closes M1 + M3. verify=True made explicit at every httpx callsite is a cheap regression guard. Removing the manual Markup() in favour of token-list + Jinja autoescape eliminates the nosec and a refactor foot-gun. Output unchanged (yaml-key + yaml-punctuation spans still render). 322 tests pass.

@lewiswigmore lewiswigmore merged commit 18efc43 into main May 29, 2026
3 checks passed
@lewiswigmore lewiswigmore deleted the security/m-tier-hardening branch May 29, 2026 09:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants