Created
September 3, 2020 12:59
-
-
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>`.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
}) | |
); | |
} |
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
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...