From 58ba991c0d370cd4f4279b5def7b941d53b22bd2 Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Sun, 28 Jun 2026 04:08:04 +0000 Subject: [PATCH] feat(extensions): opt-in CSP frame-src allowlist (HERMES_WEBUI_CSP_FRAME_EXTRA) Adds a configurable frame-src directive so an extension can embed a self-hosted web app in an iframe (e.g. an 'external app tab' pinning Grafana / Vaultwarden / a dashboard). Mirrors the existing HERMES_WEBUI_CSP_CONNECT_EXTRA knob: space-separated http(s) origins with optional *.subdomain wildcard + port, validated, ignored if malformed (directive-injection / paths / ws scheme / bad ports all rejected). - Default frame-src is 'self' only, so existing same-origin iframes are unchanged; default-off when the env var is unset. - frame-ancestors stays 'none' -- this only governs what THIS page may embed, never who may embed the WebUI. - Threaded through both the enforced and report-only CSP builders + the server.py Handler wrapper, so both headers stay identical. Docs in docs/EXTENSIONS.md. 8 new regression tests (tests/test_csp_frame_src_extra.py) cover default, valid widening, enforced parity, directive-injection rejection, path/ws/port rejection, and connect/frame independence. Existing CSP test suite still green. Note: full suite shows 2 pre-existing order-dependent flakes (test_issue1574 spawn-context, test_issue3283 import-order) that pass in isolation and are untouched by this CSP-only diff. --- CHANGELOG.md | 2 + api/helpers.py | 63 ++++++++++++++++++-- docs/EXTENSIONS.md | 31 ++++++++++ server.py | 7 ++- tests/test_csp_frame_src_extra.py | 98 +++++++++++++++++++++++++++++++ 5 files changed, 193 insertions(+), 8 deletions(-) create mode 100644 tests/test_csp_frame_src_extra.py diff --git a/CHANGELOG.md b/CHANGELOG.md index ad7f49cd..0f4566a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### Added +- **Opt-in CSP `frame-src` allowlist so an extension can embed a self-hosted web app in an iframe.** A new `HERMES_WEBUI_CSP_FRAME_EXTRA` env var widens what the WebUI page may embed in an `