# Content Security Policy

A Content Security Policy is a response header listing the origins a page is allowed to load each kind of resource from. The browser enforces it. Its main value is that a script injected into your HTML — through a comment field, a template bug or a compromised dependency — does not run, because its origin is not on the list.

- **Status:** Reference
- **Audience:** both, developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/technology/csp/

## What a policy is made of

A policy is a set of directives, each naming a resource type and the sources permitted for it. `default-src` supplies a fallback for the fetch directives that are not stated explicitly.

| Directive | Controls |
|---|---|
| `script-src` | Where executable script may come from. The directive that matters most |
| `style-src` | Stylesheets and, unless narrowed, inline `style` attributes |
| `img-src` | Images, including tracking pixels and remote images in embedded content |
| `connect-src` | The destinations `fetch`, `XMLHttpRequest`, `EventSource` and WebSocket may reach |
| `frame-ancestors` | Who may frame this page. The modern replacement for `X-Frame-Options` |
| `base-uri` | What `<base>` may be set to. Omitting it leaves an injection route open even with a tight `script-src` |
| `form-action` | Where a form may submit. Stops an injected form posting credentials elsewhere |

**`'self'`** — The page's own origin — scheme, host and port all matching.
**`'unsafe-inline'`** — Permits inline `<script>` and event-handler attributes. Its presence removes most of the protection `script-src` provides.
**`'unsafe-eval'`** — Permits the string-to-code family of calls. Some templating libraries require it, which is a reason to choose a different library.
**nonce / hash** — The disciplined way to allow one specific inline block: a per-response random nonce, or the base64 hash of the exact script text.

## Why inline script is the thing that breaks

The browser cannot distinguish an inline script you wrote from one an attacker injected into the same document — both are simply text inside a `<script>` element in your HTML. Therefore a policy that blocks injected inline script necessarily blocks yours, and every practical adoption of CSP is really a project to remove inline script from the codebase.

- Inline `<script>` blocks stop running.
- `onclick`, `onload` and every other event-handler attribute stop firing. These are easy to miss, because they look like markup.
- `javascript:` URLs stop working.
- The string-to-code calls throw, unless `'unsafe-eval'` is granted — including a string passed to a timer.
- Third-party tags that inject their own inline scripts fail in ways their vendor documentation rarely describes.

> **Careful** A nonce must be unpredictable and must change per response. A nonce baked into a cached HTML page is a constant, and a constant nonce is an allowlist entry any injected script can copy.

## Does Connect use a Content Security Policy?

**Used, and tight enough to have shaped how this documentation is written.** The application's policy permits script from `'self'` and from Google Tag Manager, and grants no `'unsafe-inline'`. Nothing else may execute.

That last clause has a direct consequence for these pages: **the documentation carries no JavaScript at all.** Not a reduced amount, not a carefully nonced amount — none. A page here is HTML and CSS, and every interactive-looking element is a native one. Anything that needed a script would need either a policy exception or a per-response nonce on pages that are otherwise static and cacheable, and neither is worth buying for a table of contents.

The side effects are ones a reader can check. Pages render with scripting disabled. Nothing depends on hydration, so a slow or blocked network cannot leave a half-rendered screen. A crawler or an assistant fetching a page gets the whole page from the first response, without executing anything — which is also why the structured data is in the served HTML rather than injected later.

> **Note** A policy is a boundary, not an audit. It does not sanitise input, it does not stop an attacker who can modify files on the origin, and it does nothing about a vulnerability in a script already on the allowlist.

## Rolling one out without breaking the site

1. Deploy `Content-Security-Policy-Report-Only` with the policy you intend, and a `report-to` endpoint.
   - Result: Violations are reported and nothing is blocked. You get an inventory of what the policy would break, from real traffic rather than from a survey of the codebase.
2. Work through the reports, removing inline handlers and moving inline blocks into files.
   - Result: The report volume falls. What is left is the genuinely hard cases: third-party tags and anything generating script at run time.
3. Switch to the enforcing header, keeping reporting enabled.
   - Result: Enforcement is live and you still learn about the paths your testing missed — browser extensions and injected content produce reports that are not your bugs.

Expect noise. Extensions, in-app browsers and corporate middleboxes inject content, and their violations arrive at your endpoint looking like site defects. Triage by whether the blocked URI is one you have ever heard of.

## Questions

### Does a CSP replace input sanitising?

No. It is a second line of defence, valuable precisely because sanitising sometimes fails. Escape and validate what you store and render; use CSP to make the consequences of a miss much smaller. Treating either as sufficient alone is how sites with both still get hit.

### Is 'unsafe-inline' ever acceptable in script-src?

It removes the protection most people adopt CSP for, so it should be a documented, time-boxed exception rather than a default. If a dependency requires it, the nonce or hash approach usually works instead, and where it does not, that is a fact about the dependency worth weighing.

### Why do these documentation pages have no interactive features?

Because the application's policy allows no inline script and the pages are static HTML and CSS by design. Native elements cover what a reference page needs — anchors, details and summary, tables — and avoiding script keeps every page readable by a client that does not execute anything, including a crawler or an assistant fetching it on someone's behalf.

## Related

- [HTTPS and TLS](https://connectbyjbrh.com/docs/technology/https/)
- [HTTP caching and revalidation](https://connectbyjbrh.com/docs/technology/http-caching/)
- [Agent security](https://connectbyjbrh.com/docs/technology/agent-security/)
- [CORS](https://connectbyjbrh.com/docs/technology/cors/)
- [JSON-LD and structured data](https://connectbyjbrh.com/docs/technology/json-ld/)
- [Security and isolation](https://connectbyjbrh.com/docs/security/)

## What this page is based on

- W3C Content Security Policy Level 3, https://www.w3.org/TR/CSP3/
- MDN — Content-Security-Policy header reference, https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy
- Connect application delivery — the served policy allows script from 'self' and Google Tag Manager with no 'unsafe-inline'
- Connect architecture source pack — docs-source/sources/GENERAL.md §4, the front end is plain ES modules with no build step
