Content Security Policy

What is CSP?

Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, including Cross-Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft, to site defacement, to malware distribution.

Steps to integrate

Add a route plugin

To add the middleware plugin to your application, simply add a file named plugin@your-plugin-name.ts to the routes folder. This file will be loaded with every request, allowing you to add headers, modify the response, and more.

Add the plugin
src/
โ””โ”€โ”€ routes/
    โ”œโ”€โ”€ plugin@csp.ts         # The plugin which runs on every request (route middleware)
    โ”œโ”€โ”€ contact/
    โ”‚   โ””โ”€โ”€ index.mdx         # https://example.com/contact
    โ”œโ”€โ”€ about/
    โ”‚   โ””โ”€โ”€ index.md          # https://example.com/about
    โ”œโ”€โ”€ index.mdx             # https://example.com/
    โ”‚
    โ””โ”€โ”€ layout.tsx            # This layout is used for all pages

Example

This template provides very permissive backward-compatible defaults. It is highly recommended that you customize it to better suit your specific use case. As this is an advanced topic, you should take a closer look at MDN or web.dev to get a better understanding of CSP. Please note that in dev mode the Vite scripts have no nonce and will report. For this reason, the example will not add csp in dev mode.

src/routes/plugin@csp.ts
import type { RequestHandler } from "@builder.io/qwik-city";
import { isDev } from "@builder.io/qwik/build";
 
export const onRequest: RequestHandler = event => {
  if (isDev) return; // Will not return CSP headers in dev mode
  const nonce = Date.now().toString(36); // Your custom nonce logic here
  event.sharedMap.set("@nonce", nonce);
  const csp = [
    `default-src 'self' 'unsafe-inline'`,
    `font-src 'self'`,
    `img-src 'self' 'unsafe-inline' data:`,
    `script-src 'self' 'unsafe-inline' https: 'nonce-${nonce}' 'strict-dynamic'`,
    `style-src 'self' 'unsafe-inline'`,
    `frame-src 'self' 'nonce-${nonce}'`,
    `object-src 'none'`,
    `base-uri 'self'`,
  ];
 
  event.headers.set("Content-Security-Policy", csp.join("; "));
};

Add it to the service worker as well

src/root.ts
import { component$, useServerData } from "@builder.io/qwik";
import {
  QwikCityProvider,
  RouterOutlet,
  ServiceWorkerRegister,
} from "@builder.io/qwik-city";
import { RouterHead } from "./components/router-head/router-head";
 
import "./global.css";
 
export default component$(() => {
  const nonce = useServerData<string | undefined>("nonce");
  return (
    <QwikCityProvider>
      <head>
        <meta charSet="utf-8" />
        <link rel="manifest" href="/manifest.json" />
        <RouterHead />
      </head>
      <body lang="en">
        <RouterOutlet />
        <ServiceWorkerRegister nonce={nonce} />
      </body>
    </QwikCityProvider>
  );
});

Custom scripts

If you have custom script tags that you need to add the nonce to, you can use the useServerData hook to get the nonce from the server and add it to your script tags.

src/components/some-component.tsx
export default component$(() => {
  const nonce = useServerData<string | undefined>("nonce");
  return (
    <div>
      <script nonce={nonce}>alert("Hello world")</script>
    </div>
  );
});

Validate your CSP

There is a great tool to validate your CSP: https://csp-evaluator.withgoogle.com/

Contributors

Thanks to all the contributors who have helped make this documentation better!

  • tzdesign
  • jordanw66