diff --git a/README.md b/README.md index 8890a49b..aa8dd313 100644 --- a/README.md +++ b/README.md @@ -330,7 +330,7 @@ For a panel served over HTTPS: TLS can end at the bundled nginx or at a proxy in front of it; such a proxy must send `X-Forwarded-Proto: https`. Failed logins are counted per client address, which the bundled nginx passes in `X-Real-IP`. A proxy that replaces the bundled nginx must set `X-Real-IP` itself, and its address must be in `LOGIN_TRUSTED_PROXIES` in `settings.py`. With a proxy in front of the bundled nginx, configure nginx's `set_real_ip_from` / `real_ip_header` for that proxy, or all clients share its address. -Every page is sent with `X-Frame-Options: DENY` and `Content-Security-Policy: object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'`: the panel cannot be shown in a frame, and its forms post only to its own origin. +Every page is sent with `X-Frame-Options: DENY` and `Content-Security-Policy: script-src 'self'; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'`: the panel runs only scripts it serves itself, cannot be shown in a frame, and its forms post only to its own origin. ### Cloud-init datasource diff --git a/webvirtcloud/middleware.py b/webvirtcloud/middleware.py index f029476a..1f653b1f 100644 --- a/webvirtcloud/middleware.py +++ b/webvirtcloud/middleware.py @@ -8,8 +8,12 @@ from operations import claims, jobs from operations.utils import busy_message, public_error_text, summary API_PREFIX = "/api/" -# the directives that need no change to the templates' inline scripts and styles -CONTENT_SECURITY_POLICY = "object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'" +# scripts only from this site: no inline script or event handler runs (inline styles still do); +# the XML editor (Ace) and the API docs (Redoc) start their workers from blob: URLs +CONTENT_SECURITY_POLICY = ( + "script-src 'self'; worker-src 'self' blob:; object-src 'none'; base-uri 'self'; form-action 'self'; " + "frame-ancestors 'none'" +) def same_origin_referer(request, default): diff --git a/webvirtcloud/tests/test_csp.py b/webvirtcloud/tests/test_csp.py index ac6998a0..a7dff24e 100644 --- a/webvirtcloud/tests/test_csp.py +++ b/webvirtcloud/tests/test_csp.py @@ -1,9 +1,18 @@ -"""Every response carries the Content-Security-Policy directives that need no -change to the templates' inline scripts and styles.""" +"""Every response carries the Content-Security-Policy, and no template has +what its script-src 'self' would block: an inline script, an inline event +handler or a javascript: URL.""" -from django.test import TestCase +import re +from pathlib import Path + +from django.conf import settings +from django.test import SimpleTestCase, TestCase from django.urls import reverse +# a