Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ import { Button } from '@mui/material'; // Avoid in packages
4. `pnpm test:unit` - Pass unit tests
5. If API changed: `pnpm proptypes && pnpm docs:api`
6. If demos changed: `pnpm docs:typescript:formatted`
7. If `.md` files changed: `pnpm vale <file1> <file2> ...` - Check prose style and grammar

## PR Title Format

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,46 @@ This vulnerability would allow the attacker to execute anything. However, with a

You can read more about CSP on the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CSP).

## How does one implement CSP?
:::info
CSP is optional. Material UI works without any CSP configuration. If your project doesn't require CSP, you can skip this guide entirely.
:::

## Static websites

If you host your site statically (for example, S3 or any CDN-only setup), you cannot use nonces because there is no server to generate a unique value per request. In that case, `'unsafe-inline'` is the only option:

```text
Content-Security-Policy:
default-src 'self';
style-src 'self' 'unsafe-inline';
script-src 'self' 'unsafe-inline';
```

## Server-Side Rendering (SSR)

### Server-Side Rendering (SSR)
With a server, you can generate a unique nonce per request for tighter security. Material UI requires the following CSP directives:

- **`style-src-elem 'nonce-<base64>'`** — Material UI uses [Emotion](https://emotion.sh/) to inject `<style>` tags. Each tag needs a matching nonce.
- **`style-src-attr 'unsafe-inline'`** — Some components apply inline `style` attributes for dynamic values (CSS custom properties, dimensions, positioning).
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should mention here this is only needed for SSR and not CSR? (saw this is mentioned in the PR description but doesn't seem to be in the doc)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I renamed the title from "dynamic hosting" back to "Server-Side Rendering (SSR)"

- **`script-src 'nonce-<base64>'`** — Only needed if you use `InitColorSchemeScript`, which renders an inline `<script>`.

A complete CSP header might look like this:

```text
Content-Security-Policy:
default-src 'self';
style-src-elem 'self' 'nonce-<base64>';
style-src-attr 'unsafe-inline';
script-src 'self' 'nonce-<base64>';
```

:::info
Some security scanners flag `style-src-attr 'unsafe-inline'` as a vulnerability. While inline styles can theoretically be used for [CSS-based data exfiltration](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy/style-src#unsafe_inline_styles), this requires an attacker to already be able to inject HTML into your page. If your application properly sanitizes user input, `style-src-attr 'unsafe-inline'` does not introduce a meaningful security risk on its own.
:::

To use CSP with Material UI (and Emotion), you need to use a nonce.
A nonce is a randomly generated string that is only used once, therefore you need to add server middleware to generate one on each request.
### Setting up the nonce

A CSP nonce is a Base 64 encoded string. You can generate one like this:
A nonce is a randomly generated string that is only used once. You need to add server middleware to generate a new one on each request. A CSP nonce is a Base 64 encoded string. You can generate one like this:

```js
import crypto from 'node:crypto';
Expand All @@ -33,11 +65,11 @@ const nonce = crypto.randomBytes(16).toString('base64'); // 128 bits of entropy

This generates a value that satisfies the [W3C CSP specification](https://w3c.github.io/webappsec-csp/#security-nonces) guidelines.

You then apply this nonce to the CSP header. A CSP header might look like this with the nonce applied:
You then apply this nonce to the CSP header:

```js
header('Content-Security-Policy').set(
`default-src 'self'; style-src 'self' 'nonce-${nonce}';`,
`default-src 'self'; style-src-elem 'self' 'nonce-${nonce}'; style-src-attr 'unsafe-inline'; script-src 'self' 'nonce-${nonce}';`,
);
```

Expand Down Expand Up @@ -73,7 +105,7 @@ function App(props) {
}
```

### CSP in Vite
### Vite

When deploying a CSP using Vite, there are specific configurations you must set up due to Vite's internal handling of assets and modules.
See [Vite Features—Content Security Policy](https://vite.dev/guide/features.html#content-security-policy-csp) for complete details.
Expand Down
Loading