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 | |
}) | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.