De-risking the deployment of a Content Security Policy
A Content Security Policy (CSP) is a security measure that helps to protect a web application and its users from a variety of threats, including cross-site scripting (XSS) attacks, data injection attacks, and malicious content loading. CSP works by specifying a set of rules that dictate what types of content are allowed to be loaded or executed by the web application. These rules can be applied to various types of content, including JavaScript, CSS, and images, as well as other types of resources such as fonts and media files.
Many feature-rich, modern front-end web application frameworks document ways of implementing the correct security headers (e.g. next.js). SvelteKit in particular makes this easy by providing configurations that match your rendering approach.
De-risking the deployment of a Content Security Policy
One way to de-risk the deployment of CSP is to use the Content-Security-Policy-Report-Only response header. This header allows web developers to test and debug their CSP rules without actually enforcing them. When a CSP rule is set to “report-only,” the web browser will still log any violations of the policy, but it will not block the content from being loaded. This can be helpful for identifying potential issues with the CSP rules before they are enforced, as well as for identifying any false positives that may occur.
CSP by example in SvelteKit
Let’s take a look at a static site with no CSP protection.

This site is built with SvelteKit so we can easily add policies to it. SvelteKit has great documentation but here’s a quick, non-exhaustive example of getting started for a static site. If you don’t have an analytics provider to report to, you can use a simple endpoint to log the CSP violations to the console. Create a new server route routes/csp-report/+server.ts
import type { RequestEvent } from "@sveltejs/kit";
interface PostParams extends Record<string, string> {}
export async function POST(event: RequestEvent<PostParams>) {
console.log(await event.request.json());
return new Response(null, {
status: 204,
});
}
In svelte.config.js, add a reportOnly configuration. This adds headers to the response that instruct the browser to report (but not enforce) CSP violations to an endpoint you control:
import adapter from "@sveltejs/adapter-auto";
import { vitePreprocess } from "@sveltejs/kit/vite";
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter(),
csp: {
reportOnly: {
"script-src": ["self"],
"report-uri": ["/csp-report"],
},
},
},
preprocess: vitePreprocess(),
};
export default config;
Now when we try to run an inline-script like in the example above, we’ll see the violation logged in the console:
{
"csp-report": {
"document-uri": "http://localhost:5173",
"referrer": "http://localhost:5173",
"violated-directive": "script-src-elem",
"effective-directive": "script-src-elem",
"original-policy": "script-src 'self' 'nonce-A50lpXOn/aMekC1NE64Xag=='; report-uri /csp-report",
"disposition": "report",
"blocked-uri": "inline",
"line-number": 1,
"column-number": 15,
"status-code": 200,
"script-sample": ""
}
}
With these reports in hand, you can now debug and fix any issues with your CSP rules during development before you fully deploy them. Once you’re ready to deploy, you can remove the reportOnly configuration in favor of directives and deploy the CSP rules as normal. In a production application, you may want to use canary deployments to further-derisk the CSP rollout until you are sure that it is working as expected.