Skip to content

fix(security): add Content-Security-Policy and Permissions-Policy headers - #197

Merged
nesquena-hermes merged 1 commit into
nesquena:masterfrom
iRonin:security/add-security-headers
Apr 10, 2026
Merged

fix(security): add Content-Security-Policy and Permissions-Policy headers#197
nesquena-hermes merged 1 commit into
nesquena:masterfrom
iRonin:security/add-security-headers

Conversation

@iRonin

@iRonin iRonin commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds Content-Security-Policy and Permissions-Policy headers to every response via _security_headers() in api/helpers.py.

Why

CSP is standard defense-in-depth that was missing from the existing security headers. The server already sets X-Content-Type-Options, X-Frame-Options, and Referrer-Policy — this completes the header suite.

CSP Policy

Directive Value Rationale
default-src 'self' Only same-origin resources
script-src 'self' Block inline/remote script injection
style-src 'self' 'unsafe-inline' Needed for theme switching
img-src 'self' data: Workspace images + data URIs
font-src 'self' data: Web fonts
connect-src 'self' Fetch/XHR only to same origin
base-uri 'self' Prevent <base> injection
form-action 'self' Prevent form hijacking

Permissions-Policy

Disables camera, microphone, geolocation — none of which this app uses.

Fixes #193

Add CSP and Permissions-Policy headers to _security_headers() for
defense-in-depth against XSS and unwanted browser feature access.

CSP policy:
  default-src 'self' — only load resources from same origin
  script-src 'self' — prevent inline/remote script injection
  style-src 'self' 'unsafe-inline' — allow themes (inline styles)
  img-src 'self' data: — allow workspace images and data URIs
  font-src 'self' data: — allow web fonts
  connect-src 'self' — only allow fetch/XHR to same origin
  base-uri 'self'; form-action 'self' — prevent base/form injection

Permissions-Policy: disable camera, microphone, geolocation.

Addresses nesquena#193.
@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Clean fix for a real gap — the existing header suite in _security_headers() was missing CSP. A few notes:

CSP policy review:

  • default-src 'self' — good baseline
  • script-src 'self' — correct, no inline scripts needed
  • style-src 'self' 'unsafe-inline' — necessary for the theme system; this is the right call rather than fighting the inline styles
  • img-src 'self' data: — needed for workspace image previews and any base64 data URIs in the UI
  • connect-src 'self' — correct; all fetch/SSE is same-origin
  • base-uri 'self'; form-action 'self' — good defensive additions

One potential issue:
If the app ever loads external fonts or icons (e.g., from a CDN), font-src 'self' data: will block them. Worth testing with the full UI — specifically the settings panel, sidebar, and any icon sets — to make sure nothing loads from an external origin that would be blocked.

Permissions-Policy:
Disabling camera, microphone, and geolocation is correct. This app has no reason to request any of those.

Note for future:
If HTTPS support lands (see PR #199), adding Strict-Transport-Security: max-age=63072000; includeSubDomains when TLS is active would complete the header suite.

Overall this looks good. Ready for maintainer review.

@nesquena

nesquena commented Apr 9, 2026

Copy link
Copy Markdown
Owner

Full Review: PR #197 — Content-Security-Policy and Permissions-Policy headers

Thanks @iRonin! This completes the security header suite.

Security Audit

The CSP policy is well-crafted:

  • default-src 'self' — correct baseline
  • script-src 'self' — no inline scripts needed (the app uses separate .js files)
  • style-src 'self' 'unsafe-inline' — necessary for theme switching which uses inline data-theme attribute + CSS variables
  • img-src 'self' data: — correct for workspace images and data URI thumbnails
  • connect-src 'self' — correct for fetch/XHR/EventSource
  • base-uri 'self' and form-action 'self' — good defense-in-depth

One potential issue: The Mermaid.js CDN (cdn.jsdelivr.net) is loaded dynamically for diagram rendering. With script-src 'self', the CDN script load will be blocked by CSP. This would break Mermaid diagram rendering in chat messages. Either add cdn.jsdelivr.net to script-src, or accept that Mermaid diagrams won't render with CSP enabled.

Similarly, Prism.js is loaded from cdnjs.cloudflare.com for syntax highlighting. Same issue.

Permissions-Policy: camera=(), microphone=(), geolocation=() is appropriate — none of these APIs are used (voice input uses the Web Speech API which isn't gated by Permissions-Policy).

Code Review

Clean — 10 lines added to the existing _security_headers() function, exactly where they belong.

Test Results

506 passed, 0 failed, 41 skipped. No regressions.

Verdict

The CSP is sound but will break Mermaid diagram rendering and Prism.js syntax highlighting since they load from CDNs. Need to either:

  1. Add cdn.jsdelivr.net and cdnjs.cloudflare.com to script-src
  2. Or document that CSP intentionally blocks CDN scripts

Otherwise approved. Please address the CDN issue.

@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Confirming the CDN situation from a codebase check — both libraries are loaded from cdn.jsdelivr.net:

Mermaid (static/ui.js, line 887):

script.src = 'https://cdn.jsdelivr.net/npm/mermaid@10.9.3/dist/mermaid.min.js';

Loaded dynamically when a mermaid-block is first encountered.

Prism (static/index.html, lines 10–12):

<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-core.min.js" ...>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/plugins/autoloader/prism-autoloader.min.js" ...>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css" ...>

Loaded statically on page load; already using SRI (integrity= attributes), which is good.

Both are on the same CDN, so the simplest fix is adding cdn.jsdelivr.net to script-src and style-src:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; base-uri 'self'; form-action 'self'

Since Prism already loads with integrity= SRI hashes, the browser will verify those scripts even when loaded from the CDN — which gives you CDN convenience plus subresource integrity protection on the static scripts. Mermaid loads dynamically (no SRI), so you're trusting the CDN for that one, same as before.

If you want to avoid the CDN dependency entirely, vendoring both into static/ is an option, but it adds maintenance overhead for upgrades.

@nesquena-hermes

Copy link
Copy Markdown
Collaborator

Applied the CDN fix raised in the review. The script-src 'self' policy was blocking Mermaid.js (loaded dynamically in ui.js from cdn.jsdelivr.net) and Prism.js (loaded statically in index.html from the same CDN).

Updated CSP policy:

  • script-src 'self' https://cdn.jsdelivr.net — covers both Mermaid (dynamic load) and Prism (static load)
  • style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net — covers Prism's CSS stylesheet from CDN
  • All other directives unchanged from the original PR

Prism's CDN loads already have integrity= SRI hashes in index.html, so the CDN allowlist here is paired with hash verification — best of both worlds.

555 tests passing on the updated branch. Ready to merge.

@nesquena-hermes
nesquena-hermes merged commit 392bc5d into nesquena:master Apr 10, 2026
nesquena-hermes pushed a commit that referenced this pull request Apr 10, 2026
The CSP script-src 'self' policy blocked all inline onclick= event handlers
in index.html (55+ handlers including toggleSettings(), switchPanel(),
filterSessions() etc.), making the settings panel, sidebar navigation, and
most interactive UI elements non-functional.

Also restores https://cdn.jsdelivr.net to both script-src and style-src
(required for Mermaid.js dynamic load in ui.js and Prism.js static load
in index.html). This was present in the original PR #197 merge but was
dropped in the v0.42.1 commit.

script-src additions:
- 'unsafe-inline': required for onclick=/oninput=/onchange= attributes
- https://cdn.jsdelivr.net: Mermaid (dynamic) and Prism (static with SRI)

style-src: retains 'unsafe-inline' + cdn.jsdelivr.net (Prism CSS)
nesquena-hermes added a commit that referenced this pull request Apr 10, 2026
…209)

The CSP script-src 'self' policy blocked all inline onclick= event handlers
in index.html (55+ handlers including toggleSettings(), switchPanel(),
filterSessions() etc.), making the settings panel, sidebar navigation, and
most interactive UI elements non-functional.

Also restores https://cdn.jsdelivr.net to both script-src and style-src
(required for Mermaid.js dynamic load in ui.js and Prism.js static load
in index.html). This was present in the original PR #197 merge but was
dropped in the v0.42.1 commit.

script-src additions:
- 'unsafe-inline': required for onclick=/oninput=/onchange= attributes
- https://cdn.jsdelivr.net: Mermaid (dynamic) and Prism (static with SRI)

style-src: retains 'unsafe-inline' + cdn.jsdelivr.net (Prism CSS)

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
JKJameson pushed a commit to JKJameson/hermes-webui that referenced this pull request Apr 25, 2026
…uena#197)

Add CSP and Permissions-Policy headers to _security_headers() for
defense-in-depth against XSS and unwanted browser feature access.

CSP policy:
  default-src 'self' — only load resources from same origin
  script-src 'self' — prevent inline/remote script injection
  style-src 'self' 'unsafe-inline' — allow themes (inline styles)
  img-src 'self' data: — allow workspace images and data URIs
  font-src 'self' data: — allow web fonts
  connect-src 'self' — only allow fetch/XHR to same origin
  base-uri 'self'; form-action 'self' — prevent base/form injection

Permissions-Policy: disable camera, microphone, geolocation.

Addresses nesquena#193.
JKJameson pushed a commit to JKJameson/hermes-webui that referenced this pull request Apr 25, 2026
…esquena#209)

The CSP script-src 'self' policy blocked all inline onclick= event handlers
in index.html (55+ handlers including toggleSettings(), switchPanel(),
filterSessions() etc.), making the settings panel, sidebar navigation, and
most interactive UI elements non-functional.

Also restores https://cdn.jsdelivr.net to both script-src and style-src
(required for Mermaid.js dynamic load in ui.js and Prism.js static load
in index.html). This was present in the original PR nesquena#197 merge but was
dropped in the v0.42.1 commit.

script-src additions:
- 'unsafe-inline': required for onclick=/oninput=/onchange= attributes
- https://cdn.jsdelivr.net: Mermaid (dynamic) and Prism (static with SRI)

style-src: retains 'unsafe-inline' + cdn.jsdelivr.net (Prism CSS)

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
roadhero added a commit to fox-in-the-box-ai/hermes-webui that referenced this pull request May 17, 2026
…ore 6 upstream tests with FOX_OVERLAY skipif (#30)

Phase 7a (fork side) of v0.6.0 upstream-separation migration
(fox-in-the-box-ai/fox-in-the-box#155). Closes nesquena#197.

## What this removes

* `api/onboarding.py` (283 LOC) — Fox's wholesale-replaced 3-step
  setup wizard. Moves to fox_overlay/webui_modules/onboarding.py
  in the monorepo (P7b nesquena#198).
* `static/setup.html`, `static/setup.css`, `static/setup.js` —
  deferred from Phase 2 because they were coupled to
  api/onboarding.py's `REPO_ROOT / "static" / "setup.html"` path
  access. P7b moves them to the overlay's webui_static/.

## What this restores

6 upstream onboarding tests Fox previously deleted (the deletion
predates the v0.6.0 migration). All restored from merge-base 9e31a2a
with a module-level `pytestmark = pytest.mark.skipif(FOX_OVERLAY)`
decorator so they:

* Pass fork CI today (skip cleanly when FOX_OVERLAY=1, which is the
  fork test env post-overlay)
* Run normally against virgin upstream content (post-Phase-8 re-point)
  where Fox doesn't replace onboarding

Restored:
* `tests/test_issue1499_keyless_onboarding.py`
* `tests/test_issue1499_onboarding_probe.py`
* `tests/test_onboarding_existing_config.py`
* `tests/test_onboarding_mvp.py`
* `tests/test_onboarding_network.py`
* `tests/test_onboarding_static.py`

## What this does NOT change

`api/routes.py` keeps its 6 inline onboarding handler blocks
(lines 1641-1648 + 2310-2326) following the Option E pattern from
Phase 4. The dispatcher hook (Phase 4) pre-empts /setup and
/api/setup/* before those lazy imports trigger — so they become
dead code after P7b ships, but stay in source until a later cleanup
pass (Phase 8+).

## Critical preservation

Fox's `_write_env_key` (line 93 of deleted onboarding.py) is used
by overlay's `webui_modules/hostname.py` — re-exported in P7b's
overlay onboarding module + hostname.py import updated to point at
the new location. **P7b must ship simultaneously with this PR's
submodule bump** to avoid hostname module-load ImportError.

## Sequencing

This PR merges FIRST. Monorepo DRAFT (P7b) bumps submodule + ships
overlay onboarding + .fox-removals consumer wiring.

## Diff summary

```
 api/onboarding.py                            | 283 ----- (deleted)
 static/setup.css                             | (deleted)
 static/setup.html                            | (deleted)
 static/setup.js                              | (deleted)
 tests/test_issue1499_keyless_onboarding.py   | + (restored)
 tests/test_issue1499_onboarding_probe.py     | + (restored)
 tests/test_onboarding_existing_config.py     | + (restored)
 tests/test_onboarding_mvp.py                 | + (restored)
 tests/test_onboarding_network.py             | + (restored)
 tests/test_onboarding_static.py              | + (restored)
```
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
…uena#197)

Add CSP and Permissions-Policy headers to _security_headers() for
defense-in-depth against XSS and unwanted browser feature access.

CSP policy:
  default-src 'self' — only load resources from same origin
  script-src 'self' — prevent inline/remote script injection
  style-src 'self' 'unsafe-inline' — allow themes (inline styles)
  img-src 'self' data: — allow workspace images and data URIs
  font-src 'self' data: — allow web fonts
  connect-src 'self' — only allow fetch/XHR to same origin
  base-uri 'self'; form-action 'self' — prevent base/form injection

Permissions-Policy: disable camera, microphone, geolocation.

Addresses nesquena#193.
SysAdminDoc pushed a commit to SysAdminDoc/hermes-webui that referenced this pull request Jun 26, 2026
…esquena#209)

The CSP script-src 'self' policy blocked all inline onclick= event handlers
in index.html (55+ handlers including toggleSettings(), switchPanel(),
filterSessions() etc.), making the settings panel, sidebar navigation, and
most interactive UI elements non-functional.

Also restores https://cdn.jsdelivr.net to both script-src and style-src
(required for Mermaid.js dynamic load in ui.js and Prism.js static load
in index.html). This was present in the original PR nesquena#197 merge but was
dropped in the v0.42.1 commit.

script-src additions:
- 'unsafe-inline': required for onclick=/oninput=/onchange= attributes
- https://cdn.jsdelivr.net: Mermaid (dynamic) and Prism (static with SRI)

style-src: retains 'unsafe-inline' + cdn.jsdelivr.net (Prism CSS)

Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
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.

Security: Missing Content-Security-Policy header

3 participants