Gatsby Content Security Policy (CSP) #10890
Replies: 36 comments 4 replies
-
I just started to look into this issue. I'd like to use Gatsby for our public web projects but the missing CSP support is a blocker for us. I'm happy to invest some time to look into this but would appreciate if someone could point me into the right direction as I'm brand-new to the codebase 🙈 |
Beta Was this translation helpful? Give feedback.
-
Not sure how much help I can be, but seconded ☝️ |
Beta Was this translation helpful? Give feedback.
-
cc @xjamundx, you'd be interested in this too, correct? With the additional consideration of |
Beta Was this translation helpful? Give feedback.
-
Happy to help as well, but new to the repo - what lifecycle APIs would be best to use? |
Beta Was this translation helpful? Give feedback.
-
in addition to the issues that need to be improved with gatsby itself, i'm curious how gatsby plugins might be addressed for similar concerns. have others already considered what would be needed beyond core? |
Beta Was this translation helpful? Give feedback.
-
Hi all, My colleague @eduards and I looked into getting CSPs working with Gatsby last week and wanted to share our progress. The changes that we made were to the static-entry.js file. You can see what we did in this GitHub comparison view. We set out to get CSPs working by hashing inline scripts and styles by adding the hashes to a meta tag that we add to the head. We found inlined scripts and styles by extracting them from the following objects in the static-entry.js file:
We are working out whether they are inlined scripts / styles by checking for either the “script” or “style” type and the “dangerouslySetInnerHTML” property on the props key With these simple changes we managed to get CSPs working with the httpEquiv="Content-Security-Policy" meta tag. From getting the proof of concept (POC) working, we came to the conclusion that making the changes in this file wouldn’t be the best way to implement CSPs. And instead would like some advice about implementing this as a plugin (if this is the right approach). A few questions to help us on the way:
|
Beta Was this translation helpful? Give feedback.
-
Hey, internally we've been using a custom plugin for this. I decided to open source it, maybe it will help some of you. https://github.com/bejamas/gatsby-plugin-csp It generates hashes for inline scripts and styles which means it sets strict policy by default. |
Beta Was this translation helpful? Give feedback.
-
@thomkrupa looks like we both had the same idea! 😂 I created a repo last night with the same name to do pretty much the same thing -> https://github.com/mattjburrows/gatsby-plugin-csp |
Beta Was this translation helpful? Give feedback.
-
@thomkrupa @mattburrowsmonzo, thanks for sharing! When I use For now I'll have to keep unsafe-inline there :( |
Beta Was this translation helpful? Give feedback.
-
By the way, at the moment I'm using gatsby-plugin-netlify (https://www.gatsbyjs.org/packages/gatsby-plugin-netlify/) to add security-related headers and it would be great to be able copy the CSP generated by your plugin to the "_headers" file generated by gatsby-plugin-netlify. I'm trying to do it now by parsing the generated "public/index.html", but I'm guessing that there could be an easier solution (e.g., if you plugin wrote the raw CSP content somewhere or made it available somehow for other Gatsby plugins to use :) Here's my current solution: DeveloPassion/website@c31120c |
Beta Was this translation helpful? Give feedback.
-
hey @dsebastien, thanks for your feedback! Feel free to create an issue(s) -> https://github.com/bejamas/gatsby-plugin-csp/issues so we can track the progress on them. |
Beta Was this translation helpful? Give feedback.
-
Hiya! This issue has gone quiet. Spooky quiet. 👻 We get a lot of issues, so we currently close issues after 30 days of inactivity. It’s been at least 20 days since the last update here. If we missed this issue or if you want to keep it open, please reply here. You can also add the label "not stale" to keep this issue open! Thanks for being a part of the Gatsby community! 💪💜 |
Beta Was this translation helpful? Give feedback.
-
ping. Don't let this one get forgotten please :) |
Beta Was this translation helpful? Give feedback.
-
Hey everyone. I'm also facing this issue with the google analytics plugin. Initially, I thought that I could simply add a 'sha256-...' entry to my script-src, but the value changes from page to page. That plugin should allow to pass a nonce so that we can whitelist it through the CSP. |
Beta Was this translation helpful? Give feedback.
-
Question for folks - is this something that should be moved in to core? What should be optional, what should be enabled for everyone? Is this something that always needs to be configured, so people would be better served by using the https://github.com/bejamas/gatsby-plugin-csp plugin? |
Beta Was this translation helpful? Give feedback.
-
Is it possible to support the webpack-recommended way of handling nonces out of the box? Each page should have a |
Beta Was this translation helpful? Give feedback.
-
Unfortunately that doesn't solve the issue of the nonce needing to be dynamically generated for each page request... without dynamic generation the nonce becomes meaningless and useless as a security measure. |
Beta Was this translation helpful? Give feedback.
-
Ideally these are the criteria that should be met for this...
https://github.com/bejamas/gatsby-plugin-csp seems to cover #1 and #2 but cannot do #3. |
Beta Was this translation helpful? Give feedback.
-
@wardpeet please pay attention to this issue. We need to handle private user data on our websites, and A+ security is our goal to bring clients onboard. Currently I'm following https://eqxtech.com/engineering/our-lambda-dance-gets-more-serious/ idea, and had another idea about substituting some constant nonce in response html with regexp. Is it possible to setup dummy CSP nonce to the scripts and styles at build time? |
Beta Was this translation helpful? Give feedback.
-
Maybe for #3 the workaround is to make something similar to what they are doing here: https://github.com/DeveloPassion/website/commit/c31120ccccefed43c266c8ef862ec696bd36c7a8. A process that at build time, get the meta tag from the index.html file, but instead of adding it to a configuration file, add the directive to the web server, or in my case, to the Lambda@Edge function. I didn't test it yet, but I'll work on that. |
Beta Was this translation helpful? Give feedback.
-
FYI, for those proposing using a meta element for CSP, it is not supported by Chrome, so if you're using the CSP plugin only, without setting the header - it is likely your CSP rules aren't working in Chrome https://caniuse.com/mdn-http_headers_csp_content-security-policy_meta-element-support |
Beta Was this translation helpful? Give feedback.
-
Actually, I think that Can I use may be incorrect for that, because testing in Chrome is indicating that it works |
Beta Was this translation helpful? Give feedback.
-
I opened mdn/browser-compat-data#6998 to discuss updating ^ |
Beta Was this translation helpful? Give feedback.
-
This is still an issue for us, as we are using Cloudfront to host our site using S3, so we cannot use a plugin to set headers We have a lambda that sets the CSP header for us, so we cannot have any inline, right now we have no choice but to keep using unsafe-inline |
Beta Was this translation helpful? Give feedback.
-
I've managed to make my site work without having to add // Remove inline scripts and styles inserted by Gatsby
export const onPreRenderHTML = ({
getHeadComponents,
replaceHeadComponents,
getPostBodyComponents,
replacePostBodyComponents
}) => {
if (process.env.NODE_ENV !== 'production') return
replaceHeadComponents(
getHeadComponents().filter(
(component) =>
!['gatsby-image-style-script', 'gatsby-image-style-noscript', 'gatsby-image-style'].includes(component.key)
)
)
replacePostBodyComponents(
getPostBodyComponents().filter((component) => !['script-loader', 'chunk-mapping'].includes(component.key))
)
} All was well, until I noticed that this breaks the redirection from <div id="___gatsby">
<div style="outline:none" tabindex="-1" id="gatsby-focus-wrapper"></div>
</div> The problem was that I removed the The only solution for now is to degrate my CSP by adding the |
Beta Was this translation helpful? Give feedback.
-
2022 and no actual fixes here? |
Beta Was this translation helpful? Give feedback.
-
Initially trying to fight against React hydration's dislike of nonce tags, we eventually pivoted to generating the sha256 sums for our production build with the following script:
Our build process looks like (gatsby build) -> (generate SHAs) -> (add SHAs at CSP-header to static file server) -> (run server) Hopefully this helps someone else looking to avoid |
Beta Was this translation helpful? Give feedback.
-
2023 and no actual fixes here? |
Beta Was this translation helpful? Give feedback.
-
Bump...this issue is effecting my website also. We host using S3 / CloudFront. My security team is NOT happy that 'unsafe-inline' is enabled for the script-src directive. Really haven't found a great solution to resolve this issue yet |
Beta Was this translation helpful? Give feedback.
-
OK, below is a Node.js script to calculate all the hashes for all produced *.html files.
Either way the solution feels clunky. import { createHash } from "crypto";
import { Parser } from "htmlparser2";
import fs from "fs";
const TARGET_FOLDER = path.join(path.resolve(), 'public');
// The list of all hashes for inserting later via `gatsby-plugin-csp` settings
let scriptHashes = [];
// Iterates through the list of HTML files to calculate all hashes
// Note, I omitted the body of `getHtmlFiles()` method
getHtmlFiles(TARGET_FOLDER).forEach(file => {
scriptHashes.push(...getShaFromTags(file, 'script'));
});
function computeHash(text) {
return `'sha256-${createHash("sha256").update(text).digest("base64")}'`;
}
// Return an array of hashes for <inputFilePath> file and the content of all instances of <tagName> tag
function getShaFromTags(inputFilePath, tagName) {
console.log(`Getting '<${tagName}>' from ${inputFilePath}`);
try {
const fileContents = fs.readFileSync(inputFilePath, { encoding: 'utf-8' });
let hashes = [];
let inScriptElement = false;
const parser = new Parser(
{
onopentag: (name, _) => {
if (name === tagName)
inScriptElement = true;
},
ontext: (text) => {
if (inScriptElement) {
hashes.push(computeHash(text));
}
},
onclosetag: (tagname) => {
if(tagname === "script")
inScriptElement = false;
}
},
{ decodeEntities: true }
);
parser.write(fileContents);
parser.end();
let uniqueHashes = [...new Set(hashes)];
return uniqueHashes;
} catch (err) {
console.error(`Could not retrieve '<${tagName}>' from ${inputFilePath}`);
throw err
}
} |
Beta Was this translation helpful? Give feedback.
-
Summary
By default Gatsby generates a couple of inline
<script>
tags and one (or more?) inline<style>
tags for each page.This is problematic when creating a Content Security Policy (CSP) for your site, as the inline scripts mean
script-src 'unsafe-inline'
needs to be configured for your CSP. This in turn undoes some of the benefits of a CSP.This issue is tracking improvements to using Gatsby with a CSP.
Useful links:
Related issues:
Basic example
Quoting @chuckharmston from issue #3758:
Motivation
Adding a Content Security Policy can be a useful safeguard for your site, Gatsby should provide options for people using CSPs.
Beta Was this translation helpful? Give feedback.
All reactions