In short
The Content Security Policy (CSP) is an HTTP response header with which a server tells the browser from which sources a page may load scripts, stylesheets, images, fonts, frames and network connections. The browser blocks everything that is not expressly allowed.
The CSP is thus the most effective protection against cross-site scripting and content injection in the client, and at the same time the only security header that can damage a running online shop if it is set incorrectly. The CSP is specified by the W3C; the current version is Content Security Policy Level 3.
How a Content Security Policy is structured
A CSP consists of directives, each covering one type of resource, followed by a list of permitted sources. Directives are separated by semicolons. A simple example:
Content-Security-Policy: default-src 'self'; script-src 'self' www.googletagmanager.com; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'
Read out, this means: by default, everything comes only from your own origin. Scripts additionally from the tag manager host. Images also as data URLs. Plugins such as Flash or Java, which would be embedded via <object>, not at all. The <base> element may only point to your own origin, and the page may only be embedded in a frame by your own origin.
The most important directives can be divided into three groups. The first group controls resources: script-src, style-src, img-src, font-src, connect-src (XHR, fetch, WebSockets), frame-src and media-src. default-src is the fallback for all resource directives that are not set explicitly. The second group controls the document itself: base-uri, form-action (where forms may be sent), frame-ancestors (who may embed the page) and object-src. The third group concerns reporting: report-uri and the newer report-to name the endpoint to which the browser reports violations.
Source expressions: from 'self' to 'strict-dynamic'
As a source, a directive can contain host names (js.stripe.com, also with a wildcard such as *.paypal.com), schemes (https:, data:) and a series of keywords in single quotation marks. 'self' stands for your own origin, 'none' forbids everything. 'unsafe-inline' allows inline scripts and inline styles, i.e. <script> blocks without src, onclick attributes and style attributes. 'unsafe-eval' allows eval() and related functions.
Because 'unsafe-inline' in script-src practically cancels out protection against XSS, the specification knows two alternatives. A nonce is a value generated randomly for each request, which the server writes both into the header ('nonce-abc123') and into every legitimate <script nonce="abc123">; an injected script does not know the value and is blocked. A hash ('sha256-…') allows exactly one inline block with known content. Finally, 'strict-dynamic' states: every script loaded by a script approved via nonce or hash is also allowed; host allowlists are then ignored. The so-called strict CSP is based on this combination.
Why host allowlists are not enough
The intuitive CSP is a list of trusted hosts. That this approach fails across the board was demonstrated by a Google research team in 2016 in the study "CSP Is Dead, Long Live CSP!". The authors examined 26,011 policies delivered on the web and found that 94.68 per cent of the policies that attempt to restrict scripts can be bypassed. The reason: as soon as a permitted host hosts a JSONP endpoint or a library such as Angular anywhere, arbitrary code can be executed through it, even though the policy is formally correct. Large CDNs and analytics hosts are almost always vulnerable in this sense. The recommendation from the study is the strict CSP with nonce and 'strict-dynamic', which manages without a host list.
That this is only slowly catching on in practice is shown by the HTTP Archive Web Almanac 2025: 21.9 per cent of the pages crawled on mobile send a CSP at all. Of the policies delivered with script-src, 92 per cent contain 'unsafe-inline'. Around 20 per cent use nonces and around 10 per cent 'strict-dynamic'. The vast majority of CSPs on the web therefore do not protect against XSS but at best against clickjacking and plugin embedding.
The figures are no reason to leave out the CSP. They are a reason to separate the directives: the document directives object-src 'none', base-uri 'self', frame-ancestors 'self' and form-action 'self' break nothing in a normal web application and each close a specific class of attack. The script-src is a project of its own.
CSP in an online shop: the checkout problem
From a CSP's point of view, an online shop is a document full of third-party scripts. The PayPal checkout button, the Stripe input field, Google Tag Manager, the cookie banner, review widgets and chat tools all load from third-party hosts. A script-src 'self' without these hosts means: the page loads, the basket works, and not a single payment button appears at checkout. The cause is in the browser log; the monitoring only shows a drop in revenue.
The hosts come from the providers' documentation, not from guesswork. For its JS SDK, PayPal names *.paypal.com, *.paypalobjects.com and *.venmo.com for script-src, frame-src, connect-src, img-src and style-src. Stripe lists js.stripe.com, *.js.stripe.com, hooks.stripe.com and api.stripe.com. An underestimated item is form-action: payment methods that work via a form redirect (3-D Secure, buy-on-invoice providers) need the host of the payment service provider in this directive. If it is missing, the customer clicks "Buy now", the browser refuses the redirect and the payment does not go through.
Report-only as a mandatory step
The header Content-Security-Policy-Report-Only evaluates a policy but blocks nothing. Every violation ends up in the browser console and, if a report-uri is set, as JSON at a report endpoint. For a shop without a CSP, this is the only justifiable way in. Two weeks of report-only across all page types (home page, category, product, basket, checkout, account pages and order confirmation) show which hosts are really needed. That is how long it takes until rare payment methods and account pages that someone only opens every few days have been run through once. Only then does -Report-Only become the enforced policy. Both headers can be sent in parallel: an enforced policy with the safe directives and, alongside it, a report-only policy that tests the stricter script-src.
There are two tools for checking a finished policy that belong together. Google's CSP Evaluator assesses the header text statically and flags hosts that can be bypassed, a missing object-src or a base-uri that has not been set. The browser's DevTools, on the other hand, show what was actually blocked on the specific page; the console names the violated directive literally, for example Refused to load the script 'https://…' because it violates the following Content Security Policy directive: "script-src 'self'". Anyone who runs through both once per release notices a new plugin script or a changed payment host before customers do. Report endpoints such as report-uri.com or your own collector gather the same reports from real visitors' browsers and are therefore the only source for pages and payment methods that never come up in your own test.
CSP in Shopware 6
Shopware 6 distinguishes three areas. The administration gets a nonce-based policy, and API routes a restrictive default template. For the storefront, the parameter shopware.security.csp_templates contains an empty string; the core therefore deliberately does not deliver a storefront CSP. The reason lies in the templates: the storefront writes inline scripts into every page, such as window.activeNavigationId and the router configuration in layout/meta.html.twig. Shopware does generate a nonce per request in the request attribute _cspNonce, but the storefront templates do not insert it into the <script> tags. A script-src without 'unsafe-inline' or a matching nonce therefore breaks the storefront on every page. A real nonce CSP for the storefront is a template project, not a quick header tweak: every theme, every plugin and every app that outputs inline scripts has to carry the nonce.
In practice this means: set the document directives on the web server immediately, roll out the script-src with the hosts of the services in use and, for the time being, with 'unsafe-inline' as report-only, and plan the switch to nonces as a project of its own. Anyone who sets the CSP without knowing the theme stops the shop. frame-ancestors replaces X-Frame-Options in all current browsers; setting both together is the clean transitional solution, especially as Shopware sends X-Frame-Options: deny in the core anyway.
Distinction and limits
The CSP is not a server protection measure. It reduces the attack surface in the user's browser; an SQL injection, an unpatched plugin or an open admin login are not affected by it. Nor does it replace output encoding in the template. Twig in Shopware escapes variables by default, and it is precisely this escaping that prevents an injected string from ending up in the document as a script at all. The CSP is the second line of defence in case the first fails, for example because of a |raw filter in the wrong place.
There is no overlap with HSTS, even though the two headers are often mentioned in the same breath. HSTS enforces the encrypted connection; the CSP governs what is executed within the loaded page. The directive upgrade-insecure-requests upgrades embedded HTTP resources to HTTPS and helps with the mixed-content problem, but only works within a page and is no substitute for HSTS. In the sense of the principle of least privilege, a CSP transfers the same idea to the client: only the sources that a page actually needs get rights.
The development is moving towards nonces and Trusted Types. Trusted Types, activated via the CSP directive require-trusted-types-for 'script', require that dangerous DOM sinks such as innerHTML only accept typed objects instead of raw strings. This also addresses DOM-based XSS, which a classic CSP does not fully cover. Browser and framework support is growing, but in shop front ends it is still the exception.
Frequently asked questions
Can I simply adopt the strict OWASP CSP?
For an application without third-party scripts, yes. In a shop, default-src 'self' without payment, tracking and font hosts stops the checkout. The way forward is via report-only, a host list and, if it is to be done properly, nonces in the templates.
What is the difference between report-uri and report-to?
report-uri is the older directive, marked as deprecated in the specification, but understood by all browsers. report-to uses the Reporting API and needs an additional Reporting-Endpoints header. Setting both in parallel is currently the safest option.
Why do I see CSP violations from browser extensions?
Extensions inject their own scripts into pages, and some browsers report this as a violation. These reports usually carry sources such as chrome-extension:// or moz-extension:// and can be filtered at the report endpoint.
Does Shopware set a CSP for the storefront?
No. The storefront template in shopware.security.csp_templates is empty; only the administration gets a nonce-based policy. A storefront CSP is set on the web server and has to take the theme's inline scripts into account.