skip to content

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%

answer

  1. two halves of one old directive
  2. element or attribute, not src or inline
  3. the element, and the text it contains
  4. event-handler attributes got their own directive
  5. absent refinement falls back to script-src

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.

solid answer

~40 s

CSP Level 3 split the blunt `script-src` in two. `script-src-elem` decides whether a script element may run: the request for the URL in its `src`, and the text of an inline block. `script-src-attr` decides whether an inline event-handler content attribute may run. They are refinements, not replacements - if you do not write `script-src-elem`, the check falls back to `script-src`, and if that is absent too, to `default-src`; `script-src-attr` follows the same chain, and `style-src-elem`/`style-src-attr` mirror it under `style-src`. Two edges catch people: `worker-src` falls back through `child-src` to `script-src`, never through `script-src-elem`; and the string-compilation check behind `'unsafe-eval'` reads `script-src`/`default-src`, so neither refinement can loosen or tighten it.

code

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

go deeper

for a junior

Recall that a Content-Security-Policy can name script sources more finely than one script-src: elem is the element, attr is the event-handler attribute written inline.

for a middle

Explain the fallback chain in order - refinement, then script-src, then default-src - and say which surface each refinement covers without guessing from the directive name.

for a senior

Show judgment about deployment: set script-src-attr 'none' to kill handler-attribute execution while the element side is still being tightened, and keep a script-src beside the refinements for older clients.

for a principal

The tradeoff is policy legibility against precision: more directives means a policy fewer engineers read correctly, so decide whether the finer split buys enough to justify the extra surface across every property.

## One directive that answered four questions A `Content-Security-Policy` is a response header built from semicolon-separated **directives**, each carrying a whitespace-separated **source list**. Through CSP Level 2 a single directive, `script-src`, answered every script question a document could raise: may this script element fetch the URL in its `src`, may the text inside an inline script block run, may an inline event-handler content attribute run, and may a string be compiled into code at runtime. An author who wanted to permit one of those and refuse another had no vocabulary for it. CSP Level 3 split the first three apart. `script-src-elem` and `script-src-attr` are **refinements** of `script-src` rather than replacements for it, and `style-src-elem` / `style-src-attr` mirror the same idea under `style-src`. ## What each one governs | Directive | Governs | Does not govern | |---|---|---| | `script-src-elem` | script elements - the request for an external `src`, and the text of an inline block | event-handler content attributes; string compilation | | `script-src-attr` | inline event-handler content attributes | script elements of either kind; string compilation | | `script-src` | whichever of those a refinement has not claimed, plus the string-compilation check in every case | resources that are not script | The usual misreading is grammatical. `script-src-attr` reads as though it governs *the attributes of a script element*, and therefore `src`. It does not. **Elem** means the script arrived as an element; **attr** means the script was written inside an attribute. The `src` URL of a script element belongs to `script-src-elem`. ## The fallback chain A refinement you did not write is not permissive by omission - the check falls through a fixed chain: 1. `script-src-elem` to `script-src` to `default-src` 2. `script-src-attr` to `script-src` to `default-src` 3. `style-src-elem` and `style-src-attr` to `style-src` to `default-src` 4. `worker-src` to `child-src` to `script-src` to `default-src` The chain stops at the first directive actually present in the policy, and that directive's source list is the one enforced. If no directive in a chain is present, the policy says nothing about that resource type and the load is not blocked by this policy - which is why a policy that writes only `script-src-elem` and omits `default-src` governs far less than its author usually assumes. ## Two edges worth knowing - **`script-src-elem` is not in `worker-src`'s chain.** A worker script falls back through `child-src` to `script-src`, skipping the element refinement entirely. Writing `script-src-elem 'self'` and expecting it to bound where workers come from is an easy and real mistake. - **Neither refinement is consulted for string compilation.** The check behind `'unsafe-eval'` reads `script-src`, falling back to `default-src`. `'wasm-unsafe-eval'` is the narrower permission on that same axis: it allows compiling and instantiating WebAssembly without allowing a string to be compiled into JavaScript, where `'unsafe-eval'` permits both. Putting either keyword into `script-src-elem` buys nothing, because that directive is not reached by the compilation check. ## Why the split is useful Injected markup very often arrives as an attribute rather than as a script element: an element smuggled into a page with an event handler on it executes without fetching anything at all. `script-src-attr 'none'` refuses that whole class outright, and it can be set independently of however permissive the element side still has to be while an application is being tightened. The reverse is useful too - a surface that must keep one legacy event handler alive can loosen `script-src-attr` alone, without widening what the page is allowed to load. ## Version note The refinements exist only from CSP Level 3. A client that predates them ignores directive names it does not recognise, so a Level 2 client reading a policy that writes `script-src-elem` and no `script-src` enforces neither of them and falls through to `default-src`. Ship the refinements **beside** a `script-src`, not instead of it, wherever older clients still matter.

  • A policy writes script-src-elem 'self' and nothing else - what governs a worker script the page starts?
    Nothing in that policy does. The worker check walks `worker-src`, then `child-src`, then `script-src`, then `default-src`; `script-src-elem` is not in that chain, so with none of those four written the worker request is unconstrained by this policy.
  • Under default-src 'self'; script-src-elem 'self', is compiling a string into code allowed?
    No. The string-compilation check reads `script-src` first and, finding none, `default-src`, whose list here holds only `'self'` and no `'unsafe-eval'`, so compilation is refused. `script-src-elem` is not consulted for that check at all, however it is written.
  • What is the style-src equivalent of the split?
    `style-src-elem` governs style elements and stylesheet links; `style-src-attr` governs inline `style` content attributes. Both fall back to `style-src` and then `default-src`, exactly as the script pair falls back to `script-src`.

Code gets into a page two ways: delivered as a parcel (a script element) or scrawled on the doorframe (an event-handler attribute). Level 3 gave the doorkeeper a separate rule for each.

saying these in an interview costs you the question

  • Thinks script-src-attr governs the src attribute of a script element
  • Assumes writing script-src-elem replaces script-src for every script check
  • Believes worker-src falls back through script-src-elem
  • Thinks script-src-elem 'unsafe-eval' permits string compilation
  • Treats an unwritten refinement as blocking rather than falling back
  • Says the split is available in CSP Level 2 policies