So I want CSP to be implemented.

In fact, there is already one official plugin that can help you do this:

gatsby-plugin-csp

Well done, it even generates the CSP sha-256 CSP hashes for you. But that’s not perfect, since it only injects the http-equiv <meta/> tag for you. In case you are a fan of CSP, you must know that Observatory does not support CSP in meta tags for now. Anyway, setting CSP in HTTP headers is better since there are some tags (e.g. frame-ancestors could not be set in <meta/>.

On the other hand, (oh by the way this site is hosted with Netlify), there is a Gatsby plugin for generating some Netlify-specific “server-side” config scripts, namely _headers and _redirects :

gatsby-plugin-netlify

Then I found that the transformHeaders: (headers, path) option seems to be configurable for my use case.

So what does my hack do?

  1. the gatsby-plugin-csp generates the CSP hashes, build the CSP string and inject it as <meta/> in each generated HTML file, in the ./public directory by default.
  2. the gatsby-plugin-netlify loops over each path, and the transformHeaders method is called upon processing of each page. You can manually update the headers argument according to the path argument. This is where the hack happens:
    • read each generated HTML file
    • extract the content value of the tag <meta name="Content-Security-policy" />
    • update the variable headers with the CSP header
    • remove the CSP meta tag in the original HTML file

That’s all! No need to hack the original two plugins at all while you don’t have to struggle writing a new Gatsby plugin from scratch.

/* gatsby-config.js */

module.exports = {
  plugins: [
    {
      resolve: `gatsby-plugin-csp`,
      options: {
        disableOnDev: false,
        reportOnly: false,
        mergeScriptHashes: true,
        mergeStyleHashes: true,
        mergeDefaultDirectives: true,
      },
    },
    {
      resolve: `gatsby-plugin-netlify`,
      options: {
        transformHeaders: (headers, path) => {
          if (path.endsWith('/')) {
            const filePath = `./public${path}index.html`;
            const rawHtml = readFileSync(filePath).toString();
            const csp = /<meta http-equiv="Content-Security-Policy" content="(.*?)"\/>/.exec(rawHtml)[1].replace(/&#x27;/g, `'`);
            headers.push(`Content-Security-Policy: ${csp}`);
            writeFileSync(filePath, rawHtml.replace(/<meta http-equiv="Content-Security-Policy" content=".*?"\/>/g, ''));
          }
          return headers;
        },
        mergeSecurityHeaders: true,
        mergeLinkHeaders: true,
        mergeCachingHeaders: true,
        generateMatchPathRewrites: true,
      },
    },
  ],
};