Defining a Custom Trusted Types Policy for a Workbox Service Worker

In my quest for all the security I can muster, I stumble into the world of trusted types and making them work with workbox and my service worker.

3 min read Filed in web w3c

In the process of recently getting ready to improve another portion of blog-pwa, I ran into an interesting error when viewing my content security policy report-only setting in regards to trusted type implementation.

A screenshot of the CSP report-only error alerting me to a sink I need to replace with a trusted type.
Justin Ribeiro

As a brief primer, trusted types is a recent specification that’s been in Chromium browsers since M83 and gives you the means to create policies that build non-spoofable typed values. The goal is to then allow said trusted type to be passed into a sink as a replacement for the string, preventing DOM-based cross-site scripting (DOM XSS) attacks. You can read more about it on the trusted types repo.

So, where’s the bug exactly? To be clear, it’s not really a bug in workbox, but rather the way I’ve defined the string that starts up the service worker. If we take a peak at the code, we can see I’m just straight up passing a string.

async __loadSw() { if ('serviceWorker' in navigator) { const wb = new Workbox('/service-worker.js'); // ... } }

That code works fine without trusted types, but the CSP error is warning me to say “hey, that string gets used as a createScriptURL, you gotta deal with that”. So, I wrote a little policy that checks to make sure it’s coming from where it should:

async __loadSw() { if ('serviceWorker' in navigator) { let swUrl; // a little function to that our service worker is coming from where it should // this is very similar to the 2.3 policy example, but you'd want to change that // host hint hint since that probably won't work for you const srcSw = url => { const parsed = new URL(url, document.baseURI); if (parsed.host === 'justinribeiro.com' || parsed.host === 'www.justinribeiro.com') { return parsed.href; } throw new TypeError('invalid sw url'); }; // feature check if we have the type if (window.trustedTypes && trustedTypes.createPolicy) { // define the policy const swPolicy = trustedTypes.createPolicy('swPolicy', { createScriptURL: src => srcSw(src), }); // create our trusted type swUrl = swPolicy.createScriptURL('service-worker.js'); } else { // we don't have trusted types or the polyfill didn't load swUrl = srcSw('service-worker.js'); } // use (hopefully) the trusted type const wb = new Workbox(swUrl); // ... } }

Not terribly complex and better than a no-op policy. The else case ideally would go away with use of the polyfill though I haven’t vetted the perf behavior of that polyfill just yet and what the impact might be.

I’m still in the process of ironing out the details of my trusted types implementation, but the above is a night little way to get started without diving into the depths.