fix(security): add Content-Security-Policy and Permissions-Policy headers - #197
Conversation
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.
|
Clean fix for a real gap — the existing header suite in CSP policy review:
One potential issue:
Note for future: Overall this looks good. Ready for maintainer review. |
Full Review: PR #197 — Content-Security-Policy and Permissions-Policy headersThanks @iRonin! This completes the security header suite. Security AuditThe CSP policy is well-crafted:
One potential issue: The Mermaid.js CDN ( Similarly, Prism.js is loaded from
Code ReviewClean — 10 lines added to the existing Test Results506 passed, 0 failed, 41 skipped. No regressions. VerdictThe CSP is sound but will break Mermaid diagram rendering and Prism.js syntax highlighting since they load from CDNs. Need to either:
Otherwise approved. Please address the CDN issue. |
|
Confirming the CDN situation from a codebase check — both libraries are loaded from Mermaid ( script.src = 'https://cdn.jsdelivr.net/npm/mermaid@10.9.3/dist/mermaid.min.js';Loaded dynamically when a Prism ( <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 ( Both are on the same CDN, so the simplest fix is adding Since Prism already loads with If you want to avoid the CDN dependency entirely, vendoring both into |
|
Applied the CDN fix raised in the review. The Updated CSP policy:
Prism's CDN loads already have 555 tests passing on the updated branch. Ready to merge. |
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)
…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>
…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.
…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>
…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) ```
…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.
…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>
Summary
Adds
Content-Security-PolicyandPermissions-Policyheaders to every response via_security_headers()inapi/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, andReferrer-Policy— this completes the header suite.CSP Policy
default-src'self'script-src'self'style-src'self' 'unsafe-inline'img-src'self' data:font-src'self' data:connect-src'self'base-uri'self'<base>injectionform-action'self'Permissions-Policy
Disables
camera,microphone,geolocation— none of which this app uses.Fixes #193