skip to content

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%

answer

  1. not every blocked value is a URL
  2. a literal marker, not a location
  3. inline covers several different constructs
  4. the sample is opt-in per directive
  5. 'report-sample', and inline violations only

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.

solid answer

~50 s

`blocked-uri` is not always a URL. When the violation is an inline script, an inline event-handler attribute or an inline style, it carries the literal string `inline`; when a string was evaluated as code, it carries the literal `eval`. So the record says *what kind* of thing was refused, not *which* one. The content itself is opt-in: `script-sample` in the legacy body (`sample` in the Reporting API body) is populated only when the directive that was violated carries the `'report-sample'` keyword-source, and even then only for inline violations, and only as a short truncated prefix. That default is deliberate, since inline content on a prescription-status page may contain data that has no business leaving the browser. What you still get without it is `effective-directive`, `source-file`, `line-number` and `column-number`, which locate the violation in the document.

code

http · 20 lines
http
POST /legacy-csp HTTP/1.1
Host: pharmacy.example
Content-Type: application/csp-report

{
  "csp-report": {
    "document-uri": "https://pharmacy.example/status?rx=8841",
    "referrer": "",
    "violated-directive": "script-src-elem",
    "effective-directive": "script-src-elem",
    "original-policy": "default-src 'self'; report-uri /legacy-csp",
    "disposition": "enforce",
    "blocked-uri": "inline",
    "status-code": 200,
    "script-sample": "",
    "source-file": "https://pharmacy.example/status",
    "line-number": 87,
    "column-number": 5
  }
}

go deeper

for a junior

The takeaway is that a violation record does not always name a URL. When the refused thing lived in the page rather than being fetched, the field holds the word inline instead of a location.

for a middle

Explain the two literal markers, inline and eval, and the two conditions for a sample: the violated directive carries 'report-sample', and the violation was inline. Note the two spellings of the sample field across delivery paths.

for a senior

Demonstrate the reading habit: pair the marker with effective-directive, source-file and line-number to identify the construct without a sample at all, and argue the sample on or off from what the page's inline content actually contains.

for a principal

The trade-off is a data one. Turning on samples makes a debugging feed into a store of page content, so the decision belongs with whoever owns retention on that collector, not with whoever is fixing the policy that week.

## blocked-uri does not always hold a URI The field name invites the assumption that a violation record always names a location, and it does not. A policy governs two different kinds of thing: resources the document fetches, and code or style the document carries inline. When the refused thing was a fetch, `blocked-uri` holds the URL. When it was inline, there is no URL to hold, so the specification puts a **literal marker** there instead. | `blocked-uri` value | What was refused | |---|---| | an absolute URL | a fetch of that resource | | the literal string `inline` | inline content in the document itself | | the literal string `eval` | a string being evaluated as code | The `inline` marker covers several distinct things: an inline script element's text, an inline event-handler content attribute, and an inline style. All of them report the same literal, which is why the marker alone never tells you *which* inline construct fired. ## Why the sample is empty by default The field that would tell you which construct fired is `script-sample` in the legacy `csp-report` body, spelled `sample` in the `CSPViolationReportBody` the Reporting API delivers. It is empty unless **both** of the following hold: 1. The directive that was violated carries the `'report-sample'` keyword-source in its source list. The sample is opt-in per directive, not a global setting. 2. The violation was an inline one. There is no sample to take from an external resource that was never executed. Even then, the value is a short truncated prefix of the offending content, not the whole thing. The default is not an oversight. Inline content in a page is page content: on a prescription-status lookup it can easily include a patient reference, a dispensing note or a session identifier interpolated into a script. A report is a request leaving the browser to a collector, so anything the specification puts in the body is something it has decided may cross that boundary. Making the sample opt-in, inline-only and truncated is the compromise between debuggability and not making the collector a second copy of the page's data. ## What the record still tells you An empty sample is not an empty record. Without it you still have: - **`effective-directive`**: the directive the browser actually checked against, which is what tells you whether this was a script, a style or something else. `violated-directive` carries the same value and is a documented historical alias, not a second piece of information. - **`source-file`, `line-number` and `column-number`**: where in the document the refused construct sits, which is frequently enough to identify it directly. - **`document-uri`**, so you know which page produced it. - **`disposition`**, so you know whether this was refused for real or merely recorded. - **`original-policy`**, the exact policy text that produced the record, which matters when more than one policy text is in circulation across a rollout. ## Turning the sample on, deliberately Adding `'report-sample'` to a directive is a considered change, not a default: - Add it to the specific directive whose violations you cannot identify, not to every directive in the policy. - Expect it only on inline violations; a refused external fetch will still report an empty sample with the keyword present. - Treat the resulting samples as page content in your collector's retention policy, because that is what they are. ## The reading that gets it wrong The common failure is to read an empty sample as evidence of a boring violation, and a `blocked-uri` of `inline` as a resource fetched from some odd scheme. Both misreadings send people looking for a network request that never happened. The correct reading of `blocked-uri: inline` with an empty sample is precise and useful in one sentence: *something written into this document, rather than fetched by it, matched no source expression in the directive named by `effective-directive`, at the position given by `source-file` and `line-number`, and this policy did not ask for a sample of it.*

  • What would you change in the policy to find out which inline construct fired, and what does that cost?
    Add the `'report-sample'` keyword-source to the source list of the directive that is being violated. The records then carry a short truncated prefix of the offending inline content. The cost is that page content now leaves the browser and lands in the collector, so the collector's retention and access rules have to be good enough to hold it.
  • The record shows effective-directive and violated-directive with the same value. Is one of them wrong?
    No. `violated-directive` is a documented historical alias that carries the same value as `effective-directive`. It is redundancy, not disagreement, and a collector should read one and ignore the other rather than trying to reconcile them as two facts.

saying these in an interview costs you the question

  • Reads blocked-uri inline as a URL scheme to look up
  • Expects a sample to be present on every violation record
  • Treats an empty sample as proof nothing inline was refused
  • Thinks violated-directive echoes the directive text you authored
  • Assumes a sample appears for refused external resources too