skip to content

A Content-Security-Policy can be delivered as an HTTP response header or as a <meta http-equiv="Content-Security-Policy"> element. What can the meta form not do, and when is it still the right choice?

level: middleimportance: should knowfreq 45%

answer

  1. the header arrives before parsing starts
  2. framing was decided before your markup ran
  3. no report-only twin exists in markup
  4. everything above the tag is unprotected
  5. srcdoc has no response to carry a header

basics

~20 s

A meta-delivered CSP is ignored for frame-ancestors, sandbox and the reporting directives, cannot be report-only, and applies only to markup after it in the document. It is the right choice when you cannot set response headers, or inside an iframe srcdoc document.

solid answer

~50 s

Both channels enforce the same directives, but the meta element is a strict subset. Browsers ignore `frame-ancestors`, `sandbox` and the reporting directives when they arrive in a meta tag, because those govern the response as a whole rather than the document's contents — the framing decision has already been made by the time the parser reaches your markup. There is also no report-only form in meta: `Content-Security-Policy-Report-Only` is header-only, so you lose the safe rollout mode entirely. And a meta policy only covers what the parser sees after it, so it must be the first thing in `<head>` or earlier markup runs unprotected. Finally a header can cover every response — JSON, downloads, error pages — while meta only exists inside HTML you author. It remains the right tool where you genuinely cannot set headers, on static hosting or in an `iframe srcdoc` document, and for tightening one page beyond the site-wide header, since multiple policies all apply and can only narrow.

go deeper

for a junior

Know that CSP normally arrives as a response header, that a meta element is the fallback, and that the meta form is a limited subset rather than an equivalent.

for a middle

Be able to name what meta cannot do — frame-ancestors, sandbox, reporting, report-only — and explain that it only covers markup appearing after it in the document.

for a senior

Show why the differences follow from when each channel is seen, and argue for setting the policy at the edge so non-HTML responses and error pages are covered too.

for a principal

Own the delivery decision across a platform: where policy lives so it cannot be forgotten per service, how per-page tightening is allowed to compose, and what a host without header control forces you to give up.

## The two channels A policy reaches the browser either as a response header: ```http Content-Security-Policy: default-src 'self'; object-src 'none' ``` or as an element in the document: ```html <meta http-equiv="Content-Security-Policy" content="default-src 'self'; object-src 'none'"> ``` They are not equivalent, and the differences all trace to one fact: the header is attached to the response before any of it is parsed, while the meta element is discovered partway through parsing the body of that response. ## What meta cannot express **`frame-ancestors`** is ignored in meta. It answers "may this response be embedded in a frame?" — a question the browser had to settle before it began building the document. By the time your markup is being parsed, the page is already framed, so honouring the directive there would be meaningless. Clickjacking protection is therefore header-only. **`sandbox`** is ignored in meta for the same reason. It constrains the document's own capabilities (origin, script execution, form submission), which are established at document creation. **The reporting directives** are ignored in meta, so a meta policy cannot direct violations anywhere. **`Content-Security-Policy-Report-Only` has no meta form at all.** Only the enforcing header name is recognised in `http-equiv`. This is the big one operationally: the standard way to introduce a policy is to run it in report-only alongside the enforcing one and watch what would break. A team that can only deliver policy by meta tag has no such mode — every change is enforced live. ## Position matters A meta policy takes effect from the point the parser encounters it. Anything above it — an inline script in the head, a stylesheet link, a preload — has already been processed and is not covered. So the element must appear as early as possible, ideally the first child of `<head>` after `<meta charset>`. This is a genuine footgun, because the page will look protected in DevTools while a script above the tag ran unpoliced. A header has no such ordering hazard: it applies to the whole document from the first byte. ## Coverage beyond HTML A header can be applied to *any* response by a server or CDN: a JSON API response, a user-uploaded file served for download, a 404 page, an image. Serving user-controlled content under `Content-Security-Policy: sandbox` is a standard containment measure, and it is only expressible as a header. A meta element exists only inside HTML you generate, so none of that is reachable. ## Policies compose, and only narrow When several policies apply to one document — say a site-wide header plus a page-level meta tag — each is enforced independently, and a resource must satisfy all of them. A meta policy can therefore only tighten what the header allows; it can never relax it. That makes meta a legitimate way for one sensitive page to impose extra restrictions on top of the platform default, and it also means you cannot use meta to "fix" a header that is too strict. ```html <!-- header allows self + cdn; this page additionally forbids the cdn --> <meta http-equiv="Content-Security-Policy" content="script-src 'self'"> ``` ## Where meta is genuinely the right tool - **Static hosting with no header control.** Some hosts serve files with a fixed header set you cannot extend; meta is then the only channel available. - **`iframe srcdoc` documents.** The document is constructed from a string in the parent, not fetched, so there is no HTTP response to carry a header. A meta element inside the srcdoc markup is the only way to give it a policy of its own. - **Locally opened or packaged HTML** that never travels over HTTP at all. - **Per-page tightening** on top of a site-wide header, as above. ## Practical guidance Use the header wherever you can set one, and put it at the edge so every response is covered uniformly, including the ones your application never renders. Reach for meta as a supplement or a fallback, and when you do, place it first in the head and remember that your clickjacking defence and your rollout mode both have to come from somewhere else.

  • Why specifically is frame-ancestors ignored when delivered in a meta tag?
    Because it governs whether the response may be embedded at all, and that decision is made when the browser loads the document into a frame — before any of its markup is parsed. A directive discovered mid-parse arrives too late to prevent the framing that already happened, so browsers ignore it rather than pretend to enforce it.
  • A site-wide header allows scripts from a CDN, and one page adds a meta CSP naming only 'self'. Which wins?
    Both apply, and the resource must satisfy each independently, so the effective rule is the intersection — that page loads no CDN scripts. Policies can only narrow. The corollary is that you can never widen a restrictive header with a meta tag, which is why a meta tag is useless as a fix for an over-tight platform policy.
  • Your team can only deliver CSP via meta on a static host. What do you lose in the rollout?
    Report-only mode, since `Content-Security-Policy-Report-Only` is header-only. You cannot observe what a candidate policy would block before enforcing it, so you have to substitute manual coverage: exercise every route in a staging deployment with the policy live, and lean on the `securitypolicyviolation` event in the page to collect what fires.

saying these in an interview costs you the question

  • Puts frame-ancestors in a meta tag for clickjacking defence
  • Thinks meta supports Content-Security-Policy-Report-Only
  • Places the meta tag after inline scripts in the head
  • Expects a meta policy to relax a stricter response header
  • Believes meta and header are fully interchangeable

context