CSP nonce values stripped by netlify during build and dev commands, cannot proceed until im helped

I have a vite/ts/react/ app that, when not interacting with netlify in anyway, be it using the dev command, or deploying it to a preview deploy, is able to properly use vite’s html.cspNonce variable in vite.config.ts to propogate a nonce placeholder wherever it’s needed. however, whenever netlify touches my html, those nonce values are removed. For example, rather than a meta tag looking like it should (<meta property=“csp-nonce” nonce=“PLACEHOLDERPLACEHOLDER”>), its stripped (<meta property=“csp-nonce” nonce>). I cannot isolate what is happening in Netlify’s process that is doing this.

I have done extensive logging throughout the entire chain of events and can confirm that the stripping occurs after the html (that is touched by an edge function that replaces the placeholder values with a real nonce value) is received in browser, but before the html is rendered and made available to console.log statements in the entry file of app as those logging statements show the stripped tags.

In other words, console.log statements confirm that the response my edge function constructs does have all nonce values in place, looking in the entry for first receipt of that same html in browser dev tools show that nonce values are in place, and once the html has been rendered in browser, that is when all nonce values have been stripped. And console.log statements in my entry ts file for app shows values have been stripped as well.

I am more than happy to elaborate further on what I’ve done so far to troubleshoot, but I am first hoping there’s a super obvious option or feature I’m overlooking that someone can point me to.

Without any site details to look at, there’s not much we can offer.

React Redux App, theres the site with the issues im facing present. I don’t mean to be resistant or obtuse, I don’t have any clue as to what you need to help. I’ll provide more information as you tell me whats needed.

If you look at the first entry in network tab in dev tools you can see the nonces are properly filled out by my edge function; from there, my entry file for app takes that nonce and stores it in the CacheProvider component that @emotion provides for nonce assignment, but the nonce values have all been stripped by then. So emotion files all get denied by my csp policy. The only thing I haven’t looked into at all is whether Netlify is ok with me manually changing headers outside of the tools you provide either in config files or settings in the netlify website. I’ve kept in all the different policies I’ve tested in case that helps in any way.

Here’s the code for my edge function:

/* eslint-disable import/no-anonymous-default-export */
import type { Context, Config } from "@netlify/edge-functions";
import { randomBytes } from "crypto";

export default async (_request: Request, context: Context) => {
	const nonce = randomBytes(16).toString('base64');
	const response = await context.next();
	let responseText = await response.text();

	// const targetStr1 = "<html lang=\"en\">/n  <head>";
	// const targetStr1 = "<html lang=\"en\">\n  <head>";
	// const replacementStr1 = `<html lang="en">/n  <head><meta property="csp-nonce" content="${nonce}" />`;
	// if (responseText.includes(targetStr1)) {
	// 	console.log(responseText)
	// 	responseText = responseText.replace(targetStr1, replacementStr1);
	// };

	const targetStr = "PLACEHOLDERPLACEHOLDER";
	const replacementStr = nonce;
	if (responseText.includes(targetStr)) {
		// console.log(responseText)
		responseText = responseText.replaceAll(targetStr, replacementStr);
		// console.log(responseText)
	};
	response.headers.set('Content-Security-Policy', `script-src 'self' 'nonce-${nonce}';`);
	// response.headers.set('Content-Security-Policy', `script-src 'self' 'nonce-${nonce}';`);

	// response.headers.set('Content-Security-Policy', `default-src 'self'; script-src 'self' 'unsafe-inline' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline' 'nonce-${nonce}'; img-src 'self'; connect-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri 'self';`);

	// response.headers.set('Content-Security-Policy', `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'nonce-${nonce}'; connect-src 'self'; img-src 'self'; base-uri 'self';`);

	// response.headers.set('Content-Security-Policy', `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'nonce-${nonce}'; img-src 'self'; connect-src 'self'; frame-ancestors 'self'; form-action 'self'; base-uri 'self';`);
	// response.headers.set('Content-Security-Policy', `default-src 'none'; script-src 'self' 'nonce-${nonce}'; connect-src 'self'; img-src 'self'; style-src 'self' 'nonce-${nonce}'; frame-ancestors 'self'; form-action 'self'; base-uri 'none';`);
	response.headers.set('X-Content-Type-Options', `nosniff`);
	response.headers.set('Referrer-Policy', `no-referrer, strict-origin-when-cross-origin`);

	const response2 = new Response(responseText, response);
	return response2;
};

export const config: Config = {
	path: "/*"
};

Could you share more details about what you mean by nonce values have been stripped? If you mean the values are stripped from the dev tools:

B

then that’s expected. Browsers (at least Chrome and Edge) don’t shhow nonce values in dev tools. You need to inspect the page in the sources tab instead of elements.

I’ve read that nonce values don’t appear in dev tools for security reasons but that they should still be accessible in code to confirm that they are there. If that’s the case then i should be able to retrieve them in entry file of my app but I can’t. all logging confirms that those nonce values are no longer present. I’ll turn on console logging and take screenshots to show the nonce values really aren’t present anymore.

ok so you should be able to see what i mean if you go to intiortega3.dev in a few minutes from this posting. the headers i set for csp through edge function is:

	response.headers.set('Content-Security-Policy', `default-src 'none'; script-src 'self' 'nonce-${nonce}'; connect-src 'self'; img-src 'self'; style-src 'self' 'nonce-${nonce}'; frame-ancestors 'self'; form-action 'self'; base-uri 'none';`);
	response.headers.set('X-Content-Type-Options', `nosniff`);
	response.headers.set('Referrer-Policy', `no-referrer, strict-origin-when-cross-origin`);

The logs in dev tools console should be populating with nonces to confirm that nonces are making it to entry file since we cant rely on them simply being in rendered html in dev tools. they are not.

If you need more information, please tell me. I apologize for not being more helpful on my end, but I’m here researching like crazy until someone can help me.

I figured out the problem at least with the particular issue I was having, accessing the nonce i was setting through <meta property="csp-nonce" nonce="PLACEHOLDERPLACEHOLDER">. Typescript might give you issues with this hence the inclusion of explicit typing: meta[property="csp-nonce"]')! as HTMLMetaElement).nonce. Using this instead of the getAttribute(‘nonce’) I have constantly been saying given as advice online is incorrect; Standards have made nonce values invisible to that method. And there you have it!