-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Nonce & Hash support for CSP Level 2 #50
Comments
Next.js components ( // _document.jsx
import { randomBytes } from "crypto";
import Document, { Html, Head, Main, NextScript } from "next/document";
export default class extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
const nonce = randomBytes(128).toString("base64");
return { ...initialProps, nonce };
}
render() {
const { nonce } = this.props;
const csp = `script-src 'self' 'unsafe-inline' 'unsafe-eval' https: http: 'nonce-${nonce}' 'strict-dynamic'`;
return (
<Html>
<Head nonce={nonce}>
<meta httpEquiv="Content-Security-Policy" content={csp} />
</Head>
<body>
<Main />
<NextScript nonce={nonce} />
</body>
</Html>
);
}
} But to generate a hash for every request, Server-Side Rendering is required. In Static Site Generation, a hash is generated on build time, and it doesn't have sufficient effect for prevention. Also, in Incremental Static Regeneration, a hash will be generated on rebuild time, and it doesn't as well. I've been considering to support nonce from the beginning, but it's currently on hold because I don't want to implement half-baked security features in next-secure-headers. If there is more need, I'd like to implement it. 🙂 |
EDIT: my example below doesn't seem to work because you cannot dynamically set nonce in headers in next.config :( Until there is an gSSP in _app.js I will go with this CSP rule per page basis. Nonce is then accessible in _document.js via res.locals.nonce (careful, it will not be present in pages that are not wrapped or have getStaticProps/static pages) import { createContentSecurityPolicyHeader } from 'next-secure-headers/lib/rules/content-security-policy';
const withCSPNonce = curry((gSSP, context) => {
if (gSSP && context) {
return Promise.resolve(gSSP(context)).then((pipedProps) => {
if (pipedProps.props) {
const nonce = v4();
const CSP = createContentSecurityPolicyHeader({
directives: {
baseUri: ["'self'"],
defaultSrc: ["'self'", ...srcWhitelist],
styleSrc: ["'self'", "'unsafe-inline'", ...srcWhitelist],
imgSrc: ["'self'", 'data:', 'blob:', 'https:', ...srcWhitelist],
objectSrc: ["'none'"],
scriptSrc: [
`'nonce-${nonce}'`,
"'strict-dynamic'",
"'unsafe-inline'",
`${(isDev && "'unsafe-eval'") || 'https:'}`,
...srcWhitelist,
],
},
});
context.res.setHeader('content-security-policy', CSP.value);
context.res.locals = { nonce };
return {
props: {
...pipedProps.props,
nonce,
},
};
}
return pipedProps;
});
}
throw Error('Either context or gSSP is not provided');
}); Thanks for the package. I was breaking my head around CSP in Next.js. I wanted to get rid of my custom express server where I used Helmet. I have slightly extended your example since I also need the nonce via I also like to apply the header to the response and not the meta tag: next.config const { v4 } = require('uuid')
//.... next.config options
async headers() {
return [
{
source: '/:path*', // attention here, the docs are incorrect (.*) <- not possible
headers: createSecureHeaders({
//...options
contentSecurityPolicy: {
directives: {
//...other rules
scriptSrc: [
`'nonce-${v4()}'`, // set nonce with the CSP headers
... _document.js export default class extends Document {
static async getInitialProps(ctx) {
// get the nonce with a regex from headers in NextJs after applying it in next.config
const [, nonce = ''] = /(?:nonce-)([a-z0-9-]+)/gi.exec(
ctx?.res?.getHeader('content-security-policy')
);
return {
...,
nonce
}
... // apply it in <Head nonce={nonce}> etc from const { nonce } = this.props; Per page if needed const withCSPNonce = gSSP => context => {
if (gSSP && context) {
return Promise.resolve(gSSP(context)).then((pipedProps) => {
if (pipedProps.props) {
const [, nonce = ''] = /(?:nonce-)([a-z0-9-]+)/gi.exec(
context?.res?.getHeader('content-security-policy')
) || []; // same way to get the nonce as in _document.js
return {
props: {
...pipedProps.props,
nonce, // apply it to pipedProps from getServerSideProps
},
};
}
return pipedProps;
});
}
throw Error('Either context or gSSP is not provided');
});
export default withCSPNonce; In a page you can wrap with the higher order function export const getServerSideProps = withCSPNonce(async(context) => {
//... logic
return {
props: {
.....
}
}
}) |
This sounds like a good candidate for generation by something like Netlify edge lambdas. Essentially create a known placeholder string in your scripts and replace it dynamically with a generated nonce. |
🌱 Feature Request
Is your feature request related to a problem? Please describe.
I am attempting to implement a CSP into my app and not use
unsafe-inline
.The way I understand CSP is that for every HTTP request I need to generate a base64 nonce / hash which gets put on the script tag and in the CSP header prefixed with
nonce-
.Describe the solution you'd like
I am not entirely sure on the solution I need here.
In my opinion it would be nice to have a solution which allows me to access a generated base64 string and pass it into both the headers and the script tags
Describe alternatives you've considered
I haven't considered any way of approaching this yet but I'll continue to try.
The text was updated successfully, but these errors were encountered: