CSP Headers for Gatsby project on Netlify
So I want CSP to be implemented.
In fact, there is already one official plugin that can help you do this:
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
:
Then I found that the transformHeaders: (headers, path)
option seems to be configurable for my use case.
So what does my hack do?
- 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. - the
gatsby-plugin-netlify
loops over each path, and thetransformHeaders
method is called upon processing of each page. You can manually update theheaders
argument according to thepath
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(/'/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,
},
},
],
};