In a Content-Security-Policy with no worker-src and no child-src, which directive decides whether a worker script loads?
answer
- fallback can be several steps
- workers are child contexts first
- worker-src, child-src, script-src, default-src
- walk stops at the first present directive
- presence ends it, not permission
basics
~10 sscript-src decides it. A worker's directive falls back along a chain - worker-src, then child-src, then script-src, then default-src - and the browser uses the first of those the policy actually contains.
solid answer
~40 sThe fallback for a fetch directive is not always a single hop to `default-src`. For a worker the browser walks an ordered chain: `worker-src`, then `child-src`, then `script-src`, then `default-src`, stopping at the first directive the policy contains. So a policy of `default-src 'none'; script-src 'self'` judges a worker by `script-src`, and a worker loaded from another host is refused even though `default-src` was never consulted. Most other fetch directives have a one-step chain straight to `default-src` - `connect-src` is the plain example. The practical consequence when tightening a policy is that adding `worker-src` changes which directive governs workers, so a list that was doing double duty for scripts and workers stops applying to workers the moment you write it.
code
http · 3 linesHTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self'go deeper
Recall that a worker is script being fetched, so with no worker-specific directive in the policy the script directive is what decides whether it loads.
State the chain in order and explain that presence, not permission, ends the walk - a directive allowing nothing still stops the fallback.
Anticipate the regression: adding a more specific directive re-points the walk, so tightening a policy can refuse resources whose own directive you never edited.
Decide how much directive granularity an estate should carry at all - every extra directive is another chain endpoint someone must reason about during a change.
## Fallback is a chain, not a hop The first thing everyone learns about `default-src` is that it stands in for fetch directives you did not write. That is true, but for some resource types the substitution is an **ordered walk** rather than a single step. The browser looks for the most specific directive first and moves outward until it finds one the policy actually contains. For a worker the order is: 1. `worker-src` - written for workers specifically 2. `child-src` - the older directive covering nested browsing contexts and workers together 3. `script-src` - because a worker is, after all, script being fetched and executed 4. `default-src` - the general fallback The walk stops at the **first directive present in the policy**, whatever its source list says. "Present" is about the directive existing, not about it permitting anything: a `script-src` that permits nothing still ends the walk, and `default-src` is never reached. ## Worked example ```http Content-Security-Policy: default-src 'none'; script-src 'self'; connect-src 'self' ``` This page starts a worker from `https://tiles.example.net/tiles-worker.js`. There is no `worker-src` and no `child-src`, so the walk lands on `script-src`, whose list is `'self'` - the document's own origin only. The worker is refused, and the directive that refused it is `script-src`, not `default-src`. The same policy on a **same-origin** worker permits it, again through `script-src`. Nothing about the worker being a worker enters into it; the only question was which directive the walk selected. ## Why a worker walks through child-src Historically workers and nested browsing contexts were governed together, and that shared directive is `child-src`. `worker-src` was added later so the two could be separated - a page can permit a nested context from one place and a worker from another. Keeping `child-src` in the chain is what stops policies written before the split from silently losing their control over workers. That is also why the chain is ordered the way it is: newest and most specific first, oldest general directive last. ## One-step chains Most fetch directives have nothing between them and `default-src`: | resource | chain the browser walks | |---|---| | a worker script | `worker-src` -> `child-src` -> `script-src` -> `default-src` | | a network connection | `connect-src` -> `default-src` | | a stylesheet | `style-src` -> `default-src` | | a classic script element | `script-src` -> `default-src` | So `connect-src` is the simple case people generalise from, and the worker chain is the one that surprises them. ## What this means when you tighten a policy Two operational consequences are worth carrying: - **Adding a directive can break something you did not touch.** A policy where `script-src 'self' https://tiles.example.net` was quietly governing both scripts and workers loses its worker coverage the moment you write `worker-src 'self'`, because the walk now stops one step earlier. The worker from the external host is refused, and nothing in the diff mentions workers being restricted. - **A directive that permits nothing still ends the walk.** Writing `child-src 'none'` because the page embeds nothing does not leave workers to `script-src`; it hands workers to `child-src`, which permits nothing, and the workers stop. ## Reading a refusal When something is refused, work the chain rather than guessing: identify the resource type, list the chain for it, and find the first directive your policy contains. That directive - and only that directive - is the one whose source list decided the outcome. The habit generalises: a fetch is always judged by exactly one directive, and the fallback rules are only about *which* one. It is also the fastest way to shorten an over-grown policy, because a directive that the walk never reaches for any resource the page actually loads is a directive you can delete.
- If a policy contains child-src 'none' and script-src 'self', which directive governs a worker?`child-src`, so the worker is refused. The walk stops at the first directive the policy contains, and presence is what ends it - a source list that permits nothing still ends the walk, so `script-src` is never consulted for that worker.
- Why can adding worker-src to an existing policy break workers that used to load?Because the walk now stops one step earlier. Workers that were being permitted by a broader `script-src` list are judged by the new directive instead, and anything the new list omits is refused - even though nothing about the script directive changed.
saying these in an interview costs you the question
- Thinks every absent directive falls straight back to default-src
- Believes the walk skips a directive whose list permits nothing
- Says a worker is governed by connect-src
- Assumes adding a narrower directive can only tighten, never break
- Treats child-src as removed rather than still in the chain