Two informational findings from the Session-0026 audit, both
framework-internal defense-in-depth. No operator action: no migration,
no schema/config/overlay change, no deployment-facing surface.
- I3: guard the dead text/html branch in email_envelope.build_envelope.
No send path passes body_html; the unused branch would emit HTML built
from possibly-unescaped user content (C1 stored-XSS class in the mail
channel). Passing body_html now raises NotImplementedError; the arg is
kept for documented future symmetry, enabling HTML mail becomes a
deliberate escape-then-unguard change.
- I4: make turnstile.verify_token async. The sync httpx.post ran inside
the async /auth/otc/request handler, blocking the event loop up to the
10s timeout on a slow CloudFlare call. It now awaits httpx.AsyncClient
via a narrow _siteverify_post seam (tests patch the seam, not the
shared AsyncClient). The sole caller (main.py) now awaits it.
Tests: full backend suite 365 passed. Added a coroutine-contract unit
test for verify_token and flipped the email_envelope HTML test to assert
the guard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per the v0.18.0 email + webhook hygiene proposal
(~/git/ohm-infra/RFC-APP-EMAIL-HYGIENE-PROPOSAL.md §1), this is the
shared envelope builder every send path will call in Slice 2. No
call-site changes yet — Slice 2 migrates email_otc / email_invite /
email._deliver / email._send_bundle to use it.
The helper centralizes the deliverability-critical headers (Date,
Message-ID, Auto-Submitted) and exposes per-kind unsubscribe
semantics (none for OTC, mailto: for invites, full one-click for
watcher notifications) as explicit kwargs rather than buried in
each call site.
15 new unit tests covering: always-present headers, Auto-Submitted
toggle, the three List-Unsubscribe shapes, plain-only vs
multipart/alternative body. Full suite: 267 passed (was 252).