Nonces instead of Hashes
Using hashes for small sites is okay when you have a small number of scripts and styles from internal and external sources but quickly becomes unwieldy when working with a large and the better approach is to use nonces. Since a nonce needs to be applied afresh each time a page is served then a placeholder is often used in your code and then replaced by server processing e.g.
<script nonce="**CSP_NONCE_PLACEHOLDER**" src="https://xyz.com/script.js"></script>
<script nonce="**CSP_NONCE_PLACEHOLDER**">
...
</script>
<style nonce="**CSP_NONCE_PLACEHOLDER**">
...
</style>
NGINX
A lot of sites use nginx and it is simple to replace the placeholder using a sub filter
set $cspNonce "${request_id}";
sub_filter_once off;
sub_filter_types *;
sub_filter **CSP_NONCE_PLACEHOLDER** $cspNonce;
The generated nonce can then be used later on when creating the Content-Security-Policy
header.
Nonces and external ...
... scripts
The strict-dynamic
keyword can be used with nonces to trust a script and simplify your CSP as you can ditch the domain whitelists (for script-src
only).
... code snippets
Over the years your legacy code base has probbaly acquired a lot of code snippets especially by tracking networks that are so useful to help you understand your users e.g. googletagmanager. At the time these scripts were pasted into your pages they were probably ignorant of nonces and so will stop working properly when they now try to create a <script />
element. Since then google have created a nonce aware script that is compatible with the latest CSP version. Comparing the scripts (old vs new) the following line of code was added, to get a nonce from the page and apply it to the <script />
tag being created
var n=d.querySelector('[nonce]');
n&&j.setAttribute('nonce',n.nonce||n.getAttribute('nonce'));
Now a lot of these type of scripts (same era) use the same pattern/technique to inject a <script />
block onto your page and it is possible to tweak the above line for each scenario.
Note: This may be less of an issue nowadays due to cookie consent policies as these snippets are often refactored out to loaded only when the site visitor has consented to their use.
Inline scripts
It is common to execute JavaScript code on user actions e.g.
<select onchange="doSomething();">
...
</select>
To avoid supplying hashes for every snippet they can be refactored to use an inline script i.e.
<select id="picker">
...
</select>
<script nonce="**CSP_NONCE_PLACEHOLDER**">
document.getElementById("picker").addEventListener('change', () => {
doSomething();
});
</script>
JavaScript URLs
Using a JavaScript URL on the href
of an anchor (<a />
) tag was often used to emulate a button but without all the default styling that came with a button and then using a library like JQuery to attach the proper event handler e.g.
<a id="clickme" href="javascript:void(0);" >Click Me!</a>
The recommendation is to convert them into button
elements and attach the event via an event listener (as above) which is also better IMO for accessibility. If there are 100s (or 1000s) of these then this might be a mammoth task and so the first instinct maybe to use a hash of the script i.e.
'sha256-rRMdkshZyJlCmDX27XnL7g3zXaxv7ei6Sg+yt4R3svU=' # javascript:void(0)
'sha256-kbHtQyYDQKz4SWMQ8OHVol3EC0t3tHEJFPCSwNG9NxQ=' # javascript:void(0);
And there are articles that indicated that this is (or was) possible but browser compatibility isn't comprehensive. Alternatives such as using href="#!"
may work better.
I've personally found that replacing the href
attribute with a role
with appropriate styling is cleaner and also improves accessibility e.g.
<a id="clickme" role="button" class="btn">Click Me!</a>
What else?
More to be added as and when...
Photo by AbsolutVision on Unsplash