The basic demo covers which hosts may serve scripts, styles, images and frames. This page covers the directives that don't behave the way that mental model predicts.
default-srcEach pair below is the same policy with and without the directive under test.
content-security-policy: default-src 'self' 'unsafe-inline' data: https://picsum.photos https://fastly.picsum.photos; connect-src 'self' https://ad.doubleclick.net;
content-security-policy: default-src 'self' 'unsafe-inline' data:; connect-src 'self' https://ad.doubleclick.net;
Two Content-Security-Policy headers. Both are
enforced independently, so a resource must satisfy both.
What happens: The form sends its fields to
example.com. With
form-action absent, a
new tab opens and the submitted values appear in its URL. With
form-action 'self', the browser
cancels that navigation and records a violation.
Why: Form submission is a navigation, not a resource
fetch. default-src therefore does not restrict it; an explicit
form-action directive does.
Opens in a new tab so you don't lose your place. Check the address bar of that tab — the values went with it.
What happens: Both cases inject
<base href="https://picsum.photos/">. With
base-uri absent, the browser
accepts it and resolves every relative URL against that origin. With
base-uri 'none', the browser
ignores the element and records a violation.
Why: default-src can restrict the requests
produced after URLs are resolved, but it does not control which document
base is accepted. That is the separate job of base-uri.
Watch it: The image below is written as
<img src="images/moustache-cakes.jpeg">. Its
resolved URL is:
…
On the hijacked page, scroll down: the worker and
<object> sections have broken too. Even root-relative
URLs like /demo/assets/worker.js resolve against the injected
base's origin, so the one element reroutes assets across the page.
What happens: The framing test acts as an attacker's page.
It can embed the version with
frame-ancestors absent, but
frame-ancestors 'none' makes the browser refuse the iframe.
Why: frame-ancestors is enforced by the page
being framed and controls who may embed it. default-src
controls what that page may load, so it provides no clickjacking
protection. This directive must be sent in a response header; browsers
ignore it in a <meta> policy.
What happens: The
active policy allows this page's nonced
script to run, but clicking the button still blocks its request to
ad.doubleclick.net.
Why: script-src controls whether code may run.
A beacon, fetch(), XHR, EventSource or WebSocket is governed
separately by connect-src, which this case limits to
'self'.
The violation fires before the network request, so this works with no internet connection.
What happens: This page always tries to start a
same-origin worker from the same URL. Under
child-src 'self' it
runs because worker-src is absent. Under
worker-src 'none' the otherwise
identical worker is blocked.
Why: The browser uses the first directive present in the
worker fallback chain:
worker-src → child-src → script-src → default-src.
A specific worker-src therefore replaces, rather than combines
with, the broader fallbacks.
Note: a worker cannot be loaded cross-origin in the first place — that's same-origin policy, not CSP. This is about whether workers run at all, not about which host they come from.
This section also reports blocked under sandbox, for an unrelated reason: a sandboxed opaque origin can't load a same-origin script.
What happens: Under
this policy, the page's
<script> blocks run, but the button's inline
onclick= does not. Clicking still updates the status badge
because an allowed script also installed a separate
addEventListener() handler.
Why: script-src-elem governs script elements,
while script-src-attr governs inline event attributes. This
policy allows the former and sets the latter to 'none'.
(handler has not run)
What happens: Both boxes load same-origin documents that
contain script. Under
object-src absent,
default-src 'self' permits both documents and their scripts
run. Under object-src 'none',
neither document loads.
Why: The parent page's script-src does not
govern a document loaded through <object> or
<embed>; the load is selected by object-src.
This matters when users can place active HTML or SVG files on your origin,
such as through an upload or attachment.
<object type="text/html">
<embed type="image/svg+xml"> — an
<img> would not run this script.
What happens: Under
this case, the first CSP header allows
picsum.photos; the second does not. The external image is
therefore blocked.
Why: Browsers enforce every CSP header as a separate policy. A request must satisfy all of them, so their combined effect is an intersection. Adding a source to Drupal's policy cannot relax another policy still being sent by a CDN or web server.
What happens: The image markup contains an
http:// URL. On an HTTPS page,
upgrade-insecure-requests
rewrites that request to https:// before it leaves the
browser.
Why: This document directive upgrades insecure
subresource URLs; it does not use default-src as a fallback.
Modern browsers already auto-upgrade some mixed-content types, including
images, so the no-CSP comparison may look the same.
The Network panel shows the URL actually requested.
…
What happens:
This case sends
sandbox allow-scripts. JavaScript still runs, but the document
receives an opaque origin, loses same-origin storage access, and cannot
submit forms or open popups.
Why: CSP's sandbox applies the restrictions
of an <iframe sandbox> to the whole document. It starts with
every sandbox restriction enabled; allow-scripts removes only
the script restriction. There is no allow-same-origin,
allow-forms or allow-popups token here.
Document origin: …
While this case is active, scroll back up and press the
form-action submit button. Nothing happens at all —
no navigation, no new tab, and the status badge never even changes,
because the submission is blocked before the submit event
fires. Silent failure is the thing to recognise here.
Every case changes one directive against the same permissive baseline, so the directive under test is the only variable.