Skip to content

Instantly share code, notes, and snippets.

@richie5um
Created September 3, 2020 12:59
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save richie5um/b2999177b27095af13ec619e44742116 to your computer and use it in GitHub Desktop.
Save richie5um/b2999177b27095af13ec619e44742116 to your computer and use it in GitHub Desktop.
Cloudflare Worker that'll; add a CSP, add a nonce (CSP) for in-line script elements (if they have the 'nonce' attribute), handle Angular 404 status responses. To get the auto-nonce to work, set the inline script in your .html files to be like `<script nonce>console.log('Hello');</script>`.
const cspConfig = {
"default-src": [
"'self'",
"blob:",
],
"script-src": [
"'self'",
"{{cspNonce}}",
],
"style-src": [
"'self'",
],
"font-src": [
"'self'",
"data:",
],
"img-src": [
"'self'",
],
"frame-src": [
],
"connect-src": [
],
"report-uri": ["https://your-account.report-uri.com/r/d/csp/reportOnly"],
"report-to": ["csp-report"]
};
function generateCspString(cspConfig, cspNonce) {
let cspSections = [];
Object.keys(cspConfig).map(function (key, index) {
let values = cspConfig[key].map(function (value) {
if (value === "{{cspNonce}}") {
return value = `'nonce-${cspNonce}'`;
}
return value;
})
let cspSection = `${key} ${values.join(" ")}`;
cspSections.push(cspSection);
});
return cspSections.join("; ");
}
function generateCspHeaders(cspConfig, cspNonce) {
return {
"Content-Security-Policy": generateCspString(cspConfig, cspNonce),
"Strict-Transport-Security": "max-age=2592000",
"X-Xss-Protection": "1; mode=block",
"X-Content-Type-Options": "nosniff",
"Referrer-Policy": "strict-origin-when-cross-origin",
"Feature-Policy": "geolocation *",
"Report-To": JSON.stringify({ "csp-report": "default", "max_age": 31536000, "endpoints": [{ "url": "https://your-account.report-uri.com/a/d/g" }], "include_subdomains": true })
};
}
let sanitiseHeaders = {
"Server": "Worker",
};
let removeHeaders = [
"Public-Key-Pins",
"X-Powered-By",
"X-AspNet-Version",
];
class AttributeRewriter {
constructor(attributeName, oldValue, newValue) {
this.attributeName = attributeName
this.oldValue = oldValue;
this.newValue = newValue;
}
element(element) {
const attribute = element.getAttribute(this.attributeName)
if (!(attribute === undefined || attribute === null)) {
console.log("AutoNoncing");
if (this.oldValue) {
element.setAttribute(
this.attributeName,
attribute.replace(this.oldValue, this.newValue));
} else {
element.setAttribute(
this.attributeName,
this.newValue);
}
}
}
}
addEventListener('fetch', event => {
return event.respondWith(addHeaders(event.request));
});
async function addHeaders(req) {
let response = await fetch(req)
let headers = new Headers(response.headers)
if (headers.has("Content-Type") && !headers.get("Content-Type").includes("text/html")) {
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: headers
});
}
let cspNonce = btoa(crypto.getRandomValues(new Uint32Array(2)));
let cspHeaders = generateCspHeaders(cspConfig, cspNonce);
Object.keys(cspHeaders).map(function (name, index) {
headers.set(name, cspHeaders[name]);
});
Object.keys(sanitiseHeaders).map(function (name, index) {
headers.set(name, sanitiseHeaders[name]);
});
removeHeaders.forEach(function (name) {
headers.delete(name);
});
// Angular 404 routing handler
let status = response.status;
let statusText = response.statusText;
if (headers.has("Content-Type") &&
headers.get("Content-Type").includes("text/html") &&
status === 404) {
status = 200;
statusText = "OK";
}
// Auto-Nonce creation
const rewriter = new HTMLRewriter()
.on("script", new AttributeRewriter("nonce", "", cspNonce));
return rewriter.transform(
new Response(response.body, {
status: status,
statusText: statusText,
headers: headers
})
);
}
@milindpatel63
Copy link

is there a way of auto inserting the nonce attribute in every script element?
i am running ghost self-hosted clog...and it seems to be adding script elements everywhere...

@richie5um
Copy link
Author

richie5um commented Oct 27, 2021

I'm sure it is possible, you'd just need to alter the AttributeRewriter in the gist above to do it on all script elements, not just the ones that have the 'nonce' attribute already added. I think you can add an 'else' to the 'if' on line 76 so that if the 'nonce' attribute is missing, it adds it anyway. HTH. Let me know if you manage to get it working.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment