Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update CSP practical guide for consistency #37227

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ page-type: guide

{{QuickLinksWithSubpages("/en-US/docs/Web/Security")}}

The [`Content-Security-Policy`](/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) HTTP header provides fine-grained control over the locations from which resources on a site can be loaded.
The [`Content-Security-Policy`](/en-US/docs/Web/HTTP/Headers/Content-Security-Policy) HTTP header provides fine-grained control over the code that can be loaded on a site, and what it is allowed to do.

## Problem

chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -19,31 +19,56 @@ CSP can also help to fix other problems, which are covered in other articles:

## Solution

Implementing a robust CSP is the best way to prevent XSS vulnerabilities.
Implementing a [strict CSP](/en-US/docs/Web/HTTP/CSP#strict_csp) is the best way to prevent XSS vulnerabilities. This uses [nonce-](/en-US/docs/Web/HTTP/CSP#nonces) or [hash-](/en-US/docs/Web/HTTP/CSP#hashes)based fetch directives to ensure that only scripts and/or styles that include the correct nonce or hash will be executed. JavaScript inserted by a hacker will simply not run.

The primary benefit of CSP comes from disabling the use of unsafe inline JavaScript. Inline JavaScript, whether reflected or stored, enables improperly-escaped user inputs to generate code that is interpreted by the web browser as JavaScript. By using CSP to disable inline JavaScript, you can eliminate almost all XSS attacks against your site.
An example is as follows:

Disabling inline JavaScript means that _all_ JavaScript must be loaded from external files via {{htmlelement("script")}} elements with `src` attributes. Inline [event handler attributes](/en-US/docs/Web/HTML/Attributes#event_handler_attributes), such as `onclick`, and JavaScript inserted directly inside `<script>` tags will fail to work. Furthermore, CSP can also disable internal stylesheets (inside {{htmlelement("style")}} tags) and inline styles (using the [`style`](/en-US/docs/Web/HTML/Global_attributes/style) attribute).
```http
Content-Security-Policy:
script-src 'nonce-416d1177-4d12-4e3b-b7c9-f6c409789fb8';
object-src 'none';
base-uri 'none';
```

The nonce should be randomly-generated by your server for every new HTTP response, so would-be attackers cannot guess it.

Strict CSPs also:

- Disable the use of unsafe [inline JavaScript](/en-US/docs/Web/HTTP/CSP#inline_javascript), meaning inline [event handler attributes](/en-US/docs/Web/HTML/Attributes#event_handler_attributes) such as `onclick`. This prevents improperly-escaped user inputs from being interpreted by the web browser as JavaScript. When a CSP includes a `script-src` directive, _all_ JavaScript must be loaded from external files via {{htmlelement("script")}} elements with `src` attributes.
chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved
- Disable the use of [risky API calls such as `eval()`](/en-US/docs/Web/HTTP/CSP#eval_and_similar_apis), which is another effect of the `script-src` directive.
- Disable all object embeds via `object-src 'none'`.
- Disable uses of the `<base>` element to set a base URI via `base-uri 'none';`.

Therefore, design websites carefully to ensure that CSP causes less problems and becomes easier to implement.
Strict CSPs are preferred over [location-based](/en-US/docs/Web/HTTP/CSP#location-based_policies) policies, also called allowlist policies, where you specify which domains scripts can be run from. This is because allowlist policies often end up allowing unsafe domains, which defeats the entire point of having a CSP, and they can get very large and unwieldy, especially if you are trying to permit services that require many third party scripts to function.

CSP can also be used to provide granular control over:

- Loading other resources such as images, video, and audio ([fetch directives](/en-US/docs/Glossary/Fetch_directive) control resource loading).
- [Web workers](/en-US/docs/Web/API/Web_Workers_API) (via [document directives](/en-US/docs/Glossary/Document_directive)).
- Embedded (i.e., {{htmlelement("iframe")}}) content.
- Navigation / form submission destinations (via [navigation directives](/en-US/docs/Glossary/Navigation_directive)).

### Steps for implementing CSP
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wonder if it's worth calling this "Steps for implementing a strict CSP"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree; it is not just that — strict CSP is the main thrust of it, but it also covers other things, like alternatives if strict CSP isn't for you, and reporting. I'm going to leave this as-is.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, fair enough.


> [!NOTE]
> Before implementing any actual CSP with the `Content-Security-Policy` header, you are advised to first test it out using the [`Content-Security-Policy-Report-Only`](/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only) HTTP header. This allows you to see if any violations would have occurred with that policy. This test requires the use of `report-to` (or the deprecated `report-uri`), as explained below.

1. Begin by trying out a policy of `default-src https:`. This is a great first goal because it disables inline code and requires browsers to use HTTPS when loading resources. It will also allow you to start to pinpoint the resources that are failing to load as a result of the policy. [`default-src`](/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src) serves as a fallback for the other CSP fetch directives.
2. Next, start to make the policy more specific, to allow the items you need, while blocking any unwanted items. You could first widen the policy remit with a reasonably locked-down policy such as `default-src 'none'; form-action 'self'; img-src 'self'; object-src 'none'; script-src 'self'; style-src 'self';`.
3. You can then go on to add specific sources as highlighted during testing; for example, `style-src 'self' https://example.com/`.
4. API endpoints should use a policy that disables resource loading and embedding; for example, `Content-Security-Policy: default-src 'none'; frame-ancestors 'none'`.
5. For existing websites with large codebases that would require too much work to disable inline scripts, you can use some of the CSP features designed to ease adoption on legacy sites. For example, the [`nonce-*`](/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#nonce-) directive requires that a `<script>` specifies the same nonce in its [`nonce`](/en-US/docs/Web/HTML/Element/script#nonce) attribute for loading to succeed, whereas the [`script-dynamic`](/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#strict-dynamic) directive extends the trust due to an accompanying nonce to other scripts that the top-level script loads.
1. Begin by trying out a strict CSP, as outlined above, then start to pinpoint resources that are failing to load as a result of the policy.
2. Make sure that external and internal scripts (included via {{htmlelement("script")}} elements) that you want to run have the correct nonce or hash inserted into the [`nonce`](/en-US/docs/Web/HTML/Element/script#nonce) or [`integrity`](/en-US/docs/Web/HTML/Element/script#integrity) attributes by the server.
chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved
3. If an allowed script goes on to load other, third-party scripts, those scripts will fail to load because they won't have the required nonce or hash. Mitigate this problem by adding the `strict-dynamic` directive, which allows the first script to pass its nonce or hash value onto scripts that it loads:
chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved

```http
Content-Security-Policy:
script-src 'nonce-416d1177-4d12-4e3b-b7c9-f6c409789fb8';
strict-dynamic;
object-src 'none';
base-uri 'none';
```

> [!NOTE]
> This does however weaken the protection offered by strict CSPs. You must make sure that the scripts you are loading are trustworthy.

4. Refactor patterns disallowed by the strict CSP, such as inline event handlers and `eval()`. For example, replace inline event handlers with [`addEventListener()`](/en-US/docs/Web/API/EventTarget/addEventListener) calls inside scripts.
5. If you are still having trouble with certain items, you can consider widening the policy to be more permissive, adding specific sources as highlighted during testing; for example, `style-src 'self' https://example.com/`. A more permissive policy such as `default-src https:` still provides some protection, disabling unsafe inline/`eval()` and only allow loading of resources (images, fonts, scripts, etc.) over HTTPS, but it is very weak compared to a strict CSP.
chrisdavidmills marked this conversation as resolved.
Show resolved Hide resolved

Keep the following points in mind:

Expand All @@ -56,7 +81,7 @@ Keep the following points in mind:
> [!NOTE] > `report-to` is preferred over the deprecated `report-uri`; however, both are still needed because `report-to` does not yet have full cross-browser support.

- Don't include any unsafe sources inside your CSP. Examples include `unsafe-inline` or `data:` URIs inside `script-src` and overly broad sources or form submission targets.
- Unless sites need the ability to execute plugins, their execution should be disabled with `object-src 'none'`.
- Unless sites need the ability to include embeds, their execution should be disabled with `object-src 'none'`.
- If you are embedding SVG sprites defined in external files via the [`<use>`](/en-US/docs/Web/SVG/Element/use) element, for example:

```svg
Expand All @@ -69,42 +94,24 @@ Keep the following points in mind:

Alternatively, if the `default-src 'none'` policy is a hard requirement, you can include the SVG sprites inline in the HTML page — see [CSP: default-src](/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src#firefox_default-src_none_svg_sprite_blocking_issue) for an example.

## Examples

Disable unsafe inline/eval and only allow loading of resources (images, fonts, scripts, etc.) over HTTPS:

```http
Content-Security-Policy: default-src https:
```

Do the same thing, but with a `<meta>` element:

```html
<meta http-equiv="Content-Security-Policy" content="default-src https:" />
```

Disable the use of unsafe inline/eval and allow everything else except plugin execution:

```http
Content-Security-Policy: default-src *; object-src 'none'
```
## Additional examples

Disable unsafe inline/eval and only load resources from same-origin with the exception of images, which can be loaded from `https://i.imgur.com`. This also disables the execution of plugins:
Disable unsafe inline/eval and only load resources from same-origin with the exception of images, which can be loaded from `https://i.imgur.com`. This also disables the execution of embeds:

```http-nolint
Content-Security-Policy: default-src 'self'; img-src 'self' https://i.imgur.com;
object-src 'none'
```

Disable unsafe inline/eval scripts and plugins, load only scripts and stylesheets from same-origin, allow fonts to be loaded from `https://fonts.gstatic.com`, and allow image loading from same-origin and `https://i.imgur.com`. Sites should aim for policies like this:
Disable unsafe inline/eval scripts and embeds, load only scripts and stylesheets from same-origin, allow fonts to be loaded from `https://fonts.gstatic.com`, and allow image loading from same-origin and `https://i.imgur.com`. Sites should aim for policies like this:

```http-nolint
Content-Security-Policy: default-src 'none'; font-src https://fonts.gstatic.com;
img-src 'self' https://i.imgur.com; object-src 'none'; script-src 'self';
style-src 'self'
```

Allow legacy sites to load scripts safely, with an increase level of trust provided by a nonce:
Allow legacy sites to load scripts safely, with an increased level of trust provided by a nonce:

```html
<script nonce="2726c7f26c">
Expand Down
Loading