skip to content

CSP

The Content-Security-Policy header, its fetch directives, nonce and hash allowlisting, and violation reports. Interviewers use it to see what you do about the XSS that gets through anyway.

part ofWeb protocols & securityoverview, primer and where to startread it →
on this pageshow

questions

26

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

A Content-Security-Policy sends script-src 'nonce-abc123'; what must an inline script element carry, and how is that value matched?

level: juniorimportance: must knowfreq 62%

basics

~10 s

The element needs a nonce content attribute holding exactly the base64-value from the policy: 'nonce-abc123' matches nonce="abc123". The comparison is a literal ASCII case-sensitive string match, with no base64 decoding and no normalisation.

open as a page

What does the Content-Security-Policy-Report-Only response header do to a resource that the policy it carries would forbid?

level: juniorimportance: must knowfreq 70%

basics

~20 s

Content-Security-Policy-Report-Only enforces nothing. A conforming browser loads and runs the resource the policy forbids, records the violation, and sends a report to the destination the policy names. It measures a policy; it does not apply one.

open as a page

What does an external `<script>` element's `integrity` attribute add when the Content-Security-Policy already allows that third-party host?

level: juniorimportance: must knowfreq 64%

basics

~20 s

Subresource Integrity pins the bytes rather than the origin: a conforming browser digests the fetched file and refuses it unless a hash in the integrity attribute matches. An allowlist only settles where script may come from.

open as a page

In a hotel loyalty statement page's Content-Security-Policy, what does `'unsafe-inline'` in `script-src` actually permit to run?

level: juniorimportance: must knowfreq 74%

basics

~20 s

'unsafe-inline' switches off the inline check for that directive's type. In a script source list it permits three positions at once: inline script element text, event-handler content attributes such as onclick, and javascript: navigation targets.

open as a page

Why does adding a nonce-source to a Content-Security-Policy script-src make an 'unsafe-inline' token in that same list stop working?

level: middleimportance: must knowfreq 55%

basics

~20 s

A source list containing any nonce-source or hash-source makes a conforming browser ignore 'unsafe-inline' for that type. The weaker keyword is left in deliberately, so clients too old to understand the newer tokens still run the page.

open as a page

A third-party `<script>` carrying an `integrity` attribute never executes and no digest mismatch is reported — which missing attribute explains it?

level: middleimportance: must knowfreq 55%

basics

~20 s

The crossorigin attribute. Subresource Integrity needs a cors-mode fetch for a cross-origin file; without crossorigin the request stays in the no-cors state, the response is opaque, and a conforming browser blocks it before any digest is compared.

open as a page

How does a client read script-src 'unsafe-inline' https: 'nonce-abcdefg' 'strict-dynamic' if it supports only CSP Level 1, only Level 2, or Level 3?

level: seniorimportance: must knowfreq 58%

basics

~20 s

A Level 1 client ignores the tokens it cannot parse and enforces 'unsafe-inline' https:. A Level 2 client parses the nonce, which switches off inline-everything, leaving https: 'nonce-abcdefg'. A Level 3 client enforces 'nonce-abcdefg' 'strict-dynamic'.

open as a page

When a statement page's Content-Security-Policy allows `script-src 'self' https://static.example.net` and that host echoes a caller-supplied callback name into a JavaScript response, what can an injected script tag achieve?

level: seniorimportance: must knowfreq 56%

basics

~20 s

A source expression matches the URL a script is fetched from, never the bytes that come back. An injected script tag pointing at that endpoint matches the allowlisted host, so the attacker-chosen callback name executes in the page's own origin.

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

Why does a Content-Security-Policy hash-source stop matching an inline script block whose text is generated per response?

level: middleimportance: should knowfreq 46%

basics

~20 s

A hash-source matches only when the digest of the UTF-8 encoding of the element's text equals the value written in the policy. Text that changes per response changes those bytes, so a precomputed digest never matches again.

open as a page

A Content-Security-Policy violation report gives blocked-uri as inline with an empty script-sample, so what does it tell you?

level: middleimportance: should knowfreq 42%

basics

~20 s

It tells you inline content in the document was refused rather than a fetch from a URL: blocked-uri carries the literal string inline, not a location. The sample is empty because the violated directive did not carry 'report-sample', which is opt-in.

open as a page

How does the report-to directive in a Content-Security-Policy change where a violation report goes and what its body looks like?

level: middleimportance: should knowfreq 46%

basics

~20 s

report-uri names a URL inside the policy and posts a csp-report body of hyphenated keys. report-to names an endpoint declared by a Reporting-Endpoints response header field, and the report arrives as application/reports+json with a camelCase body inside a typed envelope.

open as a page

Under a Content-Security-Policy, a results page renders each constituency's statement in an `<iframe srcdoc>` — which policy applies inside that frame, and why?

level: middleimportance: should knowfreq 52%

basics

~10 s

A document created from a local scheme — srcdoc, blob:, data:, a scripted about:blank — and a worker created from such a URL inherit a snapshot copy of the creating document's Content-Security-Policy list.

open as a page

In a Content-Security-Policy, which script executions does script-src-elem govern, and which does script-src-attr govern?

level: middleimportance: should knowfreq 40%

basics

~20 s

script-src-elem governs script elements - the request for an external src and the text of an inline block. script-src-attr governs inline event-handler content attributes. Each falls back to script-src, then default-src, when it is absent.

open as a page

An `integrity` attribute lists both a sha256 and a sha512 digest of one file — which does a conforming browser check?

level: middleimportance: should knowfreq 40%

basics

~20 s

Only the sha512 entry, on a client that supports it. The tokens sha256, sha384 and sha512 form an ordered set, and the client keeps only the strongest algorithm it supports, so a weaker entry beside it is never consulted.

open as a page

Why can `'unsafe-eval'` in a Content-Security-Policy not be scoped to the one legacy widget that needs it?

level: middleimportance: should knowfreq 47%

basics

~20 s

'unsafe-eval' is a document-wide capability switch rather than a source. The check fires when a string is compiled into code, where there is no URL, no element and no origin to key a narrower grant on.

open as a page

A results page's origin sends its own Content-Security-Policy and a content delivery edge appends another; how does a conforming browser combine them, and why does the origin's nonce not satisfy both?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Policies on one response are never combined. Two header fields and one comma-joined field parse into the same list, and each policy is enforced whole. A nonce-source belongs to the policy declaring it, so the other policy is unaffected by it.

open as a page

Under 'strict-dynamic', your dashboard's one nonced bootstrap computes every widget's script URL at runtime - what is that policy worth?

level: principalimportance: should knowfreq 36%

basics

~20 s

Exactly as much as the audit of every script URL that code can compute. The keyword moves the trust decision out of the policy and into the loader, so the policy no longer bounds any destination the loader is willing to fetch.

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

In a nonce-based Content-Security-Policy, which rules stop injected markup from capturing or reusing a legitimate nonce value?

level: seniorimportance: nice to knowfreq 28%

basics

~20 s

Three: the value is moved into the element's [[CryptographicNonce]] slot and the nonce content attribute is blanked, so attribute readers see nothing; the 'Is element nonceable?' check refuses a nonce on markup that looks like a half-open tag; and base-uri closes nonce retargeting.

open as a page

Why does a Content-Security-Policy violation report name the original request URL rather than the redirect target that was actually refused?

level: seniorimportance: nice to knowfreq 27%

basics

~20 s

Because the report would otherwise be an oracle: a page could point a request at a third-party endpoint and learn from its own violation report where that endpoint redirects. Reporting the pre-redirect URL keeps the record free of information the page could not already see.

open as a page

Which response header makes Subresource Integrity mandatory for a document's external scripts, and what does it block?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

The Integrity-Policy response header. It is a structured-field dictionary carrying blocked-destinations and endpoints; with blocked-destinations=(script) a conforming browser blocks any external script fetched without integrity metadata, or fetched no-CORS, and reports it.

open as a page

In a Content-Security-Policy, why does a redirect on an allowlisted host defeat a `script-src` path restriction?

level: seniorimportance: nice to knowfreq 33%

basics

~20 s

Path matching is abandoned once a request has been redirected: only the scheme, host and port of the redirect target are matched. A redirector under an allowed path therefore reaches any path on any host the policy allows.

open as a page