I'm having this error (on production) - I've changed like 20 versions of the csp headers based on responses from all over ther internet and the AI chatbots, so I'm becoming desperate regarding the headers format:
Refused to load the script '.js?id=GTM-{key}' because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
I've edited multiple versions of the following csp headers in the middleware and in the next config file:
headers: [
{
key: 'Content-Security-Policy',
value: `
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.googletagmanager https://*.google-analytics https://*.cloudflareinsights;
script-src-elem 'self' 'unsafe-inline' https://*.googletagmanager https://*.google-analytics https://*.cloudflareinsights;
style-src 'self' 'unsafe-inline' ;
img-src 'self' blob: data: https://*.google-analytics https://*.googletagmanager https://*.cloudflareinsights;
font-src 'self' data: ;
connect-src 'self' https://*.google-analytics https://*.analytics.google https://*.googletagmanager https://*.cloudflareinsights;
frame-src 'self' https://*.googletagmanager;
worker-src 'self' blob:;
child-src 'self' blob:;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`.replace(/\s{2,}/g, ' ').trim(),
},
],
But nothing is working (even with claude and chatgpt suggestions). Here is my layouts.jx file:
//other imports
import { GoogleTagManager } from "@next/third-parties/google";
export default async function RootLayout({ children }) {
const headersList = headers();
// Get user data from headers
const nonce = headersList?.get("x-nonce") || "";
return (
<html lang="en">
<GoogleTagManager gtmId="GTM-(Actual key)" nonce={nonce} />
<body className={bodyClassName}>
<noscript>
<iframe
src={`.html?id=(Actual key)`}
height="0"
width="0"
style={{ display: "none", visibility: "hidden" }}
/>
</noscript>
<div className="wrapper ovh mm-page mm-slideout">
<div>
{children}
</div>
</div>
</body>
</html>
);
}
Regarding the nonce, here is how I stored it in the middleware:
async function generateNonce() {
const array = new Uint8Array(16);
crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
function applyHeaders(response) {
const nonce = generateNonce();
const securityHeaders = {
'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
'Surrogate-Control': 'no-store',
'X-Frame-Options': 'DENY',
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
'X-XSS-Protection': '1; mode=block',
'Content-Security-Policy': `
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.googletagmanager https://*.google-analytics https://*.cloudflareinsights;
script-src-elem 'self' 'unsafe-inline' https://*.googletagmanager https://*.google-analytics https://*.cloudflareinsights;
style-src 'self' 'unsafe-inline' ;
img-src 'self' blob: data: https://*.google-analytics https://*.googletagmanager https://*.cloudflareinsights;
font-src 'self' data: ;
connect-src 'self' https://*.google-analytics https://*.analytics.google https://*.googletagmanager https://*.cloudflareinsights;
frame-src 'self' https://*.googletagmanager;
worker-src 'self' blob:;
child-src 'self' blob:;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`.replace(/\s{2,}/g, ' ').trim()
};
Object.entries(securityHeaders).forEach(([key, value]) => {
response.headers.set(key, value);
});
response.headers.set('x-nonce', nonce)
return response;
}
I'm having this error (on production) - I've changed like 20 versions of the csp headers based on responses from all over ther internet and the AI chatbots, so I'm becoming desperate regarding the headers format:
Refused to load the script 'https://www.googletagmanager.com/gtm.js?id=GTM-{key}' because it violates the following Content Security Policy directive: "script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
I've edited multiple versions of the following csp headers in the middleware and in the next config file:
headers: [
{
key: 'Content-Security-Policy',
value: `
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.googletagmanager.com https://*.google-analytics.com https://*.cloudflareinsights.com;
script-src-elem 'self' 'unsafe-inline' https://*.googletagmanager.com https://*.google-analytics.com https://*.cloudflareinsights.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' blob: data: https://*.google-analytics.com https://*.googletagmanager.com https://*.cloudflareinsights.com;
font-src 'self' data: https://fonts.gstatic.com;
connect-src 'self' https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com https://*.cloudflareinsights.com;
frame-src 'self' https://*.googletagmanager.com;
worker-src 'self' blob:;
child-src 'self' blob:;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`.replace(/\s{2,}/g, ' ').trim(),
},
],
But nothing is working (even with claude and chatgpt suggestions). Here is my layouts.jx file:
//other imports
import { GoogleTagManager } from "@next/third-parties/google";
export default async function RootLayout({ children }) {
const headersList = headers();
// Get user data from headers
const nonce = headersList?.get("x-nonce") || "";
return (
<html lang="en">
<GoogleTagManager gtmId="GTM-(Actual key)" nonce={nonce} />
<body className={bodyClassName}>
<noscript>
<iframe
src={`https://www.googletagmanager.com/ns.html?id=(Actual key)`}
height="0"
width="0"
style={{ display: "none", visibility: "hidden" }}
/>
</noscript>
<div className="wrapper ovh mm-page mm-slideout">
<div>
{children}
</div>
</div>
</body>
</html>
);
}
Regarding the nonce, here is how I stored it in the middleware:
async function generateNonce() {
const array = new Uint8Array(16);
crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}
function applyHeaders(response) {
const nonce = generateNonce();
const securityHeaders = {
'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
'Surrogate-Control': 'no-store',
'X-Frame-Options': 'DENY',
'X-Content-Type-Options': 'nosniff',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
'X-XSS-Protection': '1; mode=block',
'Content-Security-Policy': `
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.googletagmanager.com https://*.google-analytics.com https://*.cloudflareinsights.com;
script-src-elem 'self' 'unsafe-inline' https://*.googletagmanager.com https://*.google-analytics.com https://*.cloudflareinsights.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' blob: data: https://*.google-analytics.com https://*.googletagmanager.com https://*.cloudflareinsights.com;
font-src 'self' data: https://fonts.gstatic.com;
connect-src 'self' https://*.google-analytics.com https://*.analytics.google.com https://*.googletagmanager.com https://*.cloudflareinsights.com;
frame-src 'self' https://*.googletagmanager.com;
worker-src 'self' blob:;
child-src 'self' blob:;
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`.replace(/\s{2,}/g, ' ').trim()
};
Object.entries(securityHeaders).forEach(([key, value]) => {
response.headers.set(key, value);
});
response.headers.set('x-nonce', nonce)
return response;
}
Share
Improve this question
edited Jan 19 at 18:50
Drew Reese
203k17 gold badges234 silver badges266 bronze badges
asked Jan 19 at 16:52
AliAbAliAb
5693 gold badges8 silver badges33 bronze badges
1 Answer
Reset to default 1This page is your guide: https://developers.google.com/tag-platform/security/guides/csp.
Note that if you use Google Analytics with Signal you may have to add a lot of google domains, just follow the link. If you want to support all of them (all countries), you might have to add 187 domains to img-src and connect-src.