Safety
CSP worker-src vs script-src for Workers and Service Workers
April 14, 2023
CSP worker-src vs script-src is two different script surfaces. script-src allowlists JavaScript that runs in the page. worker-src allowlists URLs that may be used as Worker, SharedWorker, and Service Worker scripts. If you only tighten script-src and leave worker-src to a loose default-src or a fallback, a XSS gadget can still start a worker from an origin you never meant to trust. Most brochure sites that do not register workers should set worker-src 'none'. Sites that do use a first-party service worker should name that origin — usually 'self' — not *. RoastMyOpsec reads CSP quality on the landing HTML. It does not score missing worker-src as a standalone F, does not register a service worker, and does not exploit worker scripts.
The practical difference
A page script is script-src. A DedicatedWorker, SharedWorker, or Service Worker constructor/register call is a worker-src fetch. child-src is the older combined directive for workers and frames; modern policies split worker-src and frame-src. If worker-src is omitted, browsers fall back to child-src, then script-src, then default-src — which is why a 'tight' script-src still surprises teams that never named workers.
Service workers persist and intercept fetches. That is a product feature, not a reason to set worker-src *. Pair a first-party worker with Cache-Control on HTML and a clear unregister path on logout if the worker held authenticated caches. See service workers vs Cache-Control.
How to choose worker-src
Marketing site with no workers: worker-src 'none' on day one, same reflex as object-src 'none'. PWA or offline shell: worker-src 'self' and keep the worker file on your origin with SRI or a hashed filename. Third-party 'analytics worker' URLs: name that origin only if you truly need it; prefer first-party analytics. Report-Only first if you are not sure whether a tag manager starts a worker.
| Directive | What it allowlists | Does not replace | Takeaway |
|---|---|---|---|
| script-src | Classic page JavaScript | Worker constructor URLs | Necessary, not sufficient |
| worker-src 'none' | No workers | script-src / object-src | Default for brochure sites |
| worker-src 'self' | First-party worker scripts | Cache-Control on HTML | PWAs and first-party SW |
| child-src | Legacy workers + frames mix | A split modern policy | Prefer worker-src + frame-src |
When worker-src wins
worker-src wins when the page CSP looks strict but nobody named workers. script-src nonce/hash still wins for inline XSS on the document. Neither is a substitute for not shipping a service worker that caches personalized HTML as public.
What the roast can prove
CSP presence and quality signals on the HTML response can surface. Missing worker-src is not scored as its own finding. Confirm the directive (or the fallback chain) in the header. Pair with object-src and connect-src so a worker cannot fetch the whole internet even if it starts.
Common mistakes
The first mistake is assuming script-src covers Worker() and navigator.serviceWorker.register.
The second mistake is worker-src https: because one CDN hosts a helper file.
The third mistake is a service worker on 'self' that caches logged-in HTML as if it were a public asset.
Free audit the URL you own
RoastMyOpsec is a defensive public-surface roast: headers, cookies, sensitive paths, and more — no exploit payloads. Start with the free audit, then open the vault if the blurred findings look expensive.
Free audit nowFAQ
- Does script-src already control service workers?
- Not reliably. Set worker-src explicitly. Fallback to script-src only happens when worker-src and child-src are both absent.
- Should a brochure site set worker-src 'none'?
- Yes if it does not register workers. Missing it is not a roast F by itself if default-src is already tight.
- Does RoastMyOpsec score worker-src?
- It grades CSP quality overall. It does not register a service worker or treat worker-src as the only CSP check.