skip to content

questions

5

In a Content-Security-Policy, what does default-src do for the fetch directives you did not write?

level: juniorimportance: must knowfreq 76%

answer

  1. covers the directive you did not write
  2. fallback, not inheritance
  3. writing one replaces the whole list
  4. fetch directives only
  5. duplicate name discarded, first wins

basics

~20 s

default-src supplies the source list for any fetch directive absent from the policy. A directive you do write takes nothing from it - the written list replaces the default wholly for that resource type, and an empty list blocks everything.

solid answer

~50 s

`default-src` is the fallback for **fetch** directives that the policy does not name. If there is no `script-src`, script loads are judged by `default-src`; if there is no `default-src` either, that resource type is unrestricted. The trap is that the fallback is a substitution, not an inheritance: the moment you write `script-src https://tiles.example.net`, that directive takes nothing from `default-src`, so scripts from your own origin are now refused unless you also write `'self'` into that same list. Two parsing rules come with it - directive names are case-insensitive, and if the same directive name appears twice inside one policy the first occurrence wins and the duplicate is discarded rather than merged. Directives outside the fetch family never consult `default-src` at all, so `default-src 'none'` is not a blanket lock on everything a policy can control.

code

http · 3 lines
http
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Security-Policy: default-src 'self'; connect-src 'self' https://alerts.example.net

go deeper

for a junior

Recall that default-src fills in for fetch directives the policy did not write, and that writing a directive means writing its whole list, including your own origin.

for a middle

Explain absent versus empty versus 'none', that the fallback covers fetch directives only, and what a duplicate directive name does during parsing.

for a senior

Diagnose from the header alone: read each resource type against exactly one directive, and spot policies assembled from fragments where a duplicate silently discards half the intent.

for a principal

Set the house rule - closed default, explicit per-directive lists, one place in the response path that owns the header - so policies stay auditable rather than accreting hosts.

## The rule, stated precisely A `Content-Security-Policy` header value is a list of directives separated by semicolons. When a conforming browser is about to fetch something - a script, a stylesheet, a network connection - it looks for the **fetch directive** that governs that resource type. If the policy does not contain it, the browser uses `default-src` instead. If the policy contains neither, nothing restricts that resource type at all. That is the whole of the fallback, and two things follow that catch people out. ## Writing a directive replaces, never merges This is the single most common mistake in a first policy: ```http Content-Security-Policy: default-src 'self'; script-src https://tiles.example.net ``` The author's intention is almost always "my own origin, **plus** that host". What the policy says is "for script, **only** that host". A written directive does not take the `default-src` list and add to it - it stands entirely on its own. The page's own bundle is now refused, and the fix is to write the full list you meant: ```http Content-Security-Policy: default-src 'self'; script-src 'self' https://tiles.example.net ``` Read a policy the way the parser does: for each resource type, find the one directive that governs it and read *only* that directive's tokens. ## Absent, empty and 'none' are three different things | the policy contains | what governs a connection | effect | |---|---|---| | `default-src 'self'` and no `connect-src` | `default-src` | own origin only, by fallback | | `default-src 'self'; connect-src https://alerts.example.net` | `connect-src` | that host only; the own-origin allowance is gone | | `default-src 'self'; connect-src 'none'` | `connect-src` | nothing at all | | `default-src 'self'; connect-src` (no tokens) | `connect-src` | nothing at all | | no `connect-src` and no `default-src` | nothing | unrestricted | The fourth row is worth staring at. A directive written with an **empty** source list is present, so no fallback occurs, and an empty list permits nothing. "I left it blank so it would use the default" produces the opposite of what the author wanted. ## What default-src does not cover `default-src` is the fallback for the **fetch** directive family only - the directives that decide where a resource may be loaded from, such as `script-src`, `style-src`, `connect-src`, `worker-src` and `manifest-src`. Directives that control something other than a fetch - document-level and navigation-level directives, and the reporting directives - do not consult `default-src` at all. A policy of `default-src 'none'` is therefore not a blanket lock on everything CSP can express; it locks the fetching, and anything outside that family remains unset unless you write it. ## Two parsing rules that come with the fallback 1. **Directive names are ASCII case-insensitive.** `Script-Src` and `script-src` are the same directive. Source expressions are not uniformly case-insensitive, so do not generalise the rule to the tokens. 2. **A duplicate directive name inside one policy is discarded.** If a policy contains `script-src 'self'` and later `script-src https://tiles.example.net`, the first occurrence wins and the second is ignored - not merged, not overriding. This happens most often when a policy is assembled by string concatenation somewhere in the response path and two fragments both contribute the same directive. ## Writing one from scratch For a small public page the practical order is: - start with `default-src 'none'`, so every fetch the page performs shows up as a refusal - add one fetch directive at a time for what the page genuinely needs - script, style, connections - write each list complete, including `'self'` where the page loads its own assets, because nothing is inherited - re-read the finished header as a list of independent decisions, one per resource type The result is a policy where every token is there because something needed it, which is a policy you can still defend six months later. The alternative - a long allowlist grown by adding a host whenever something broke - is a policy nobody can shorten, because nobody can say which entries are still load-bearing.

  • A policy reads default-src 'self'; style-src https://tiles.example.net and the page's own stylesheet stops loading. What happened?
    `style-src` is written, so it governs stylesheets on its own and takes nothing from `default-src`. Its list names one external host and not the document's origin, so the page's own stylesheet is refused. Writing `style-src 'self' https://tiles.example.net` restores it.
  • What happens when the same directive name appears twice in one Content-Security-Policy header value?
    The first occurrence wins and the later one is discarded; the two source lists are not merged. It is a parsing rule, not a precedence rule, and it usually bites when a policy string is assembled from fragments in more than one place in the response path.
  • Does default-src 'none' restrict everything a Content-Security-Policy can control?
    No. `default-src` is the fallback for the fetch directive family only - the directives deciding where resources may be loaded from. Directives that govern something other than a fetch never consult it, so they stay unset unless the policy writes them explicitly.

It behaves like a default setting that a specific setting replaces outright rather than extends: the moment you fill in the specific field, the default stops contributing anything to it.

saying these in an interview costs you the question

  • Thinks a written directive is added to the default-src list
  • Believes an empty source list falls back to default-src
  • Says default-src restricts every directive in the policy
  • Expects two identical directive names to be merged
  • Assumes a missing directive blocks rather than falls back
open as a page

In a Content-Security-Policy source list, what do the keyword sources 'self' and 'none' each mean?

level: juniorimportance: must knowfreq 72%

basics

~20 s

'self' matches the protected document's own origin - same scheme, host and port. 'none' matches nothing, so the directive carrying it permits no source at all. Both are keyword-sources, and the single quotes are part of the grammar.

open as a page

In a Content-Security-Policy source list, which URLs does the source expression * match, and which does it miss?

level: middleimportance: should knowfreq 45%

basics

~20 s

A bare * matches any host and port over HTTP(S), plus URLs in the document's own scheme. It does not reach data:, blob: or filesystem: URLs - those only load if the policy names that scheme explicitly.

open as a page

In a Content-Security-Policy with no worker-src and no child-src, which directive decides whether a worker script loads?

level: middleimportance: should knowfreq 42%

basics

~10 s

script-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.

open as a page

A script allowlisted by host and path still loads after the request is redirected elsewhere on that host - why?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

Path matching in a Content-Security-Policy source expression applies only while the request has not been redirected. Once the redirect count is above zero the browser skips the path-part and matches scheme, host and port alone.

open as a page