skip to content

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

level: juniorimportance: must knowfreq 72%

answer

  1. two keywords, both quoted
  2. one means here, one means nowhere
  3. origin tuple: scheme, host, port
  4. no subdomains, no inline, no blob:
  5. empty source list equals 'none'

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.

solid answer

~50 s

A `Content-Security-Policy` response header is a list of directives, and each directive carries a whitespace-separated source list. `'self'` is a keyword-source meaning the origin the document itself was served from - scheme, host and port must all match, so `https://status.example.com` does not cover `https://tiles.example.com` or any subdomain. `'none'` is the opposite: it is the whole source list on its own and permits nothing, which is why `default-src 'none'` is the usual starting point for a policy you then open up directive by directive. The quotes matter: written as bare `self`, the parser reads a host-source for a host literally named `self`. Two edges catch people out - an inline script element has no URL, so no host-source or `'self'` ever covers it, and a `blob:` URL is not matched by `'self'` even though the document created it.

code

http · 3 lines
http
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'self'

go deeper

for a junior

Recall the two meanings and the quoting: 'self' is this document's own origin, 'none' is nothing at all, and both are written inside single quotes.

for a middle

Be able to explain origin as a scheme-host-port tuple, why subdomains and other ports are excluded, and why an absent directive behaves nothing like an empty one.

for a senior

Show that you start a policy from a closed default and open it per directive, and that you know 'self' resolves per document, so one header string means different things on different origins.

for a principal

Frame the choice: a policy written in origin keywords survives domain moves and audits cleanly, while a long host allowlist becomes a standing inventory problem across an estate.

## What a source list is made of The value of a `Content-Security-Policy` response header is a **serialized-policy**: directives separated by semicolons. A directive is a name (`script-src`, `style-src`, `connect-src`, `default-src`) followed by a **serialized-source-list** - zero or more tokens separated by whitespace. Every token is a **source-expression**, and there are only a few families: - a **scheme-source**, written as a scheme and a colon: `https:` - a **host-source**, which may carry a scheme-part, a host-part, a port-part and a path-part: `https://tiles.example.net:443/v1/` - a **keyword-source**, written inside single quotes: `'self'`, `'none'` The quoting is grammar, not style. `'self'` is a keyword; `self` without quotes parses as a host-source for a host named `self`, which will never match anything and silently weakens nothing - it just sits there doing nothing while you believe the origin is allowed. ## 'self' is an origin, not a company `'self'` matches a URL whose origin is the same as the protected document's origin. Origin means the **tuple of scheme, host and port**, so all three must agree: - a page at `https://status.example.com` allows `https://status.example.com/updates.js` under `script-src 'self'` - it does **not** allow `https://tiles.example.com/lib.js` - different host - it does **not** allow `http://status.example.com/updates.js` - different scheme - it does **not** allow subdomains; a wildcard in the host-part of a host-source is a separate expression, not something `'self'` implies Because `'self'` resolves against the document that received the policy, the *same* header string means different things on a staging origin and a production origin. That is usually the behaviour you want, and it is why a policy written entirely in terms of `'self'` survives a domain change untouched. One documented allowance is worth knowing: where the document itself is on `http` on the default port, `'self'` also accepts the `https` and `wss` variants of the same host. It is an upgrade allowance, never a downgrade - an `https` document is not thereby allowed to load `http` sources. ## The two edges juniors meet first - **Inline code has no URL.** An inline `<script>` element is not fetched from anywhere, so no host-source and no `'self'` can describe it. That is why a page's own inline script stops running the day a policy ships: the source list simply has nothing in it that can apply to inline code. - **`blob:` is not 'self'.** A `blob:` URL created by the document is still not matched by `'self'`; non-network schemes have to be named explicitly as a scheme-source. ## 'none' is an empty guarantee `'none'` is grammatically the *entire* source list - the grammar allows either one or more source expressions, or the single token `'none'`. Practically: - `script-src 'none'` permits no script source of any kind for that directive - writing `'none'` beside a host (`script-src 'none' https://tiles.example.net`) does not lock anything down; the host is still permitted, and the keyword contributes nothing - a directive written with an **empty** source list has the same effect as `'none'`: nothing is permitted ## The four cases side by side | what you write | what the directive permits | where the value comes from | |---|---|---| | `connect-src 'self'` | connections to the document's own origin only | the directive itself | | `connect-src 'none'` | nothing at all | the directive itself | | `connect-src` with no tokens | nothing at all | the directive itself | | no `connect-src` at all | whatever `default-src` permits; everything if there is no `default-src` | the fallback | That last row is the one to hold on to: **absent and empty are opposites.** An absent fetch directive falls back; an empty one blocks. ## Who enforces this The server only *declares* the policy on the response. A conforming browser is what enforces it, on the document that received it. Nothing about `'self'` or `'none'` protects an endpoint from a client that is not a browser, and a policy does not stop an injection from being written into the page - it stops the injected reference from being loaded or run. Starting from `default-src 'none'` and adding back one directive at a time is the practical way to write a first policy for a small page, because every refusal you then see names something you actually need.

  • A page served from https://status.example.com sends script-src 'self'. Does that permit https://status.example.com:8443/app.js?
    No. `'self'` matches the document's origin tuple, and the port is part of that tuple. The document is on the default https port, so a script on port 8443 is a different origin and is refused. Permitting it needs an explicit host-source carrying that port-part.
  • What is the practical difference between omitting connect-src entirely and writing connect-src 'none'?
    Omitting it makes the browser fall back to `default-src`, so the directive's behaviour is whatever that says - possibly everything, if no `default-src` was written either. Writing `connect-src 'none'` permits nothing regardless of `default-src`. Absent means inherit; empty or `'none'` means block.
  • Why is default-src 'none' a common first line rather than default-src 'self'?
    It fails closed. With `'none'` every fetch the page needs shows up as a refusal you have to add back deliberately, so the finished policy lists exactly what the page uses. `'self'` silently permits the whole of your own origin, including endpoints you never meant to serve to this page.

saying these in an interview costs you the question

  • Thinks 'self' also covers subdomains of the document's host
  • Thinks 'self' permits the page's own inline script element
  • Writes self without the single quotes and expects a keyword
  • Believes 'none' beside a host still blocks that host
  • Treats an omitted directive as equivalent to 'none'
  • Claims a policy stops the injection rather than the execution