Skip to content

Commit

Permalink
Merge branch 'chore/1.0.0-rc.4' into extend-ssg-csp
Browse files Browse the repository at this point in the history
  • Loading branch information
Baroshem authored Nov 9, 2023
2 parents 702d14e + 34f81ac commit 75b3999
Show file tree
Hide file tree
Showing 3 changed files with 883 additions and 38 deletions.
213 changes: 176 additions & 37 deletions docs/content/1.documentation/2.headers/1.csp.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,87 @@ type CSPSandboxValue =
```
::

## Static site generation (SSG)
## Strict CSP

Nuxt Security helps you increase the security of your site via **strict CSP** support for both SSR and SSG applications.

For further reading about Strict CSP and how to handle specific cases, please consult our [Adanced Section about Strict CSP](/documentation/advanced/strict-csp)

- For SSR applications, Nuxt Security implements strict CSP via nonces. A one-time cryptographically-generated random nonce is generated at runtime by the server for each request of a page.
- For SSG applications, Nuxt Security implements strict CSP via hashes. At static build-time, Nuxt Security computes the SHA hashes of the elements that are allowed to execute on your site.

If you want to enable strict CSP, we recommend that you configure the `contentSecurityPolicy` header as follows:

```ts
export default defineNuxtConfig({
security: {
nonce: true, // Enables HTML nonce support in SSR mode
ssg: {
hashScripts: true, // Enables CSP hash support for scripts in SSG mode
hashStyles: false // Disables CSP hash support for styles in SSG mode (recommended)
},
headers: {
contentSecurityPolicy: {
'script-src': [
"'self'", // Fallback value, will be ignored by most modern browsers (level 3)
"https:", // Fallback value, will be ignored by most modern browsers (level 3)
"'unsafe-inline'", // Fallback value, will be ignored by almost any browser (level 2)
"'strict-dynamic'", // Strict CSP via 'strict-dynamic', supported by most modern browsers (level 3)
"'nonce-{{nonce}}'" // Enables CSP nonce support for scripts in SSR mode, supported by almost any browser (level 2)
],
'style-src': [
"'self'", // Enables loading of stylesheets hosted on your own domain
"https:", // For increased security, replace by the specific hosting domain or file name of your external stylesheets
"'unsafe-inline'" // Recommended default for most Nuxt apps
]
}
}
}
})
```

These default values will enable Strict CSP in the majority of cases.


## Server Side Rendering (SSR)

Nuxt Security provides first-class support of SSR apps via 'strict-dynamic' and nonces.

To enable Strict CSP in SSR mode, you will need to set the `nonce` option and the `"'nonce-{{nonce}}'"` placeholders:

```ts
export default defineNuxtConfig({
security: {
nonce: true, // Enables HTML nonce support in SSR mode
},
headers: {
contentSecurityPolicy: {
'script-src': [
"'strict-dynamic'", // Modify with your custom CSP sources
"'nonce-{{nonce}}'" // Enables CSP nonce support for scripts in SSR mode, supported by almost any browser (level 2)
]
}
}
})
```

- `nonce`: Set this option to `true` to parse all `<script>`, `<link>` and `<style>` tags in your application and add the nonce value to these. The module parses all inline elements as well as all external resources.
- `"'nonce-{{nonce}}'"` placeholder: Include this value in any individual policy that you want to be governed by nonce.


::alert{type="warning"}
Our default recommendation is to avoid using the `"'nonce-{{nonce}}'"` placeholder on `style-src` policy.
<br>
⚠ This is because Nuxt's mechanism for Client-Side hydration of styles could be blocked by CSP in that case.
<br>
For further discussion and alternatives, please refer to our [Advanced Section on Strict CSP](/documentation/advanced/strict-csp).
::


_Note: Nonce only works for SSR. The `nonce` option and the `"'nonce-{{nonce}}'"` placeholders are ignored when you build your app for SSG via `nuxi generate`._


## Static Site Generation (SSG)

This module is meant to work with SSR apps, but you can also use this module in SSG apps where you will get a Content Security Policy (CSP) support via `<meta http-equiv>` tag.

Expand All @@ -137,55 +217,71 @@ This will result in following code being added to your static app `<head>` tag:
ℹ Read more about this [here](https://content-security-policy.com/examples/meta/).
::

By default, Nuxt Security will generate script hashes for you. If you do not want this functionality you can disable it like following:

To enable Strict CSP for SSG apps, you will need to set the `ssg` option:

```ts
export default defineNuxtConfig({
security: {
ssg: {
hashScripts: false
hashScripts: true, // Enables CSP hash support for scripts in SSG mode
hashStyles: false // Disables CSP hash support for styles in SSG mode (recommended)
},
headers: {
contentSecurityPolicy: {
'script-src': [
"'strict-dynamic'", // Modify with your custom CSP sources
// The nonce-{{nonce}} placeholder is not required and will be ignored in SSG mode
]
}
}
}
})
```

## Nonce
Nuxt Security will generate script hashes and style hashes for you according to the `ssg` option:
- `hashScripts`: Set this option to `true` to parse all inline scripts as well as all external scripts. Nuxt-Security will compute the hashes of inline scripts and find the `integrity` attributes of external scripts, and will add them to your `script-src` policy.
- `hashStyles`: Set this option to `true` to parse all inline styles as well as all external styles. Nuxt-Security will compute the hashes of inline styles and find the `integrity` attributes of external styles, and will add them to your `style-src` policy.

::alert{type="warning"}
Our default recommendation is to avoid setting the `ssg: hashStyles` option to `true`.
<br>
⚠ This is because Nuxt's mechanism for Client-Side hydration of styles could be blocked by CSP in that case.
<br>
For further discussion and alternatives, please refer to our [Advanced Section on CSP](/documentation/advanced/strict-csp).
::

::alert{type="info"}
For SSG apps with `'strict-dynamic'` mode, external scripts will only be allowed to execute if they carry an integrity attribute.
<br>
Please see our section on Integrity Hashes [below](#integrity-hashes-for-ssg)
::

_Note: Hashes only work for SSG. The `ssg` options are ignored when you build your app for SSR via `nuxi build`._

To further increase CSP security, you can use a [nonce-based strict csp](https://web.dev/strict-csp/#what-is-a-strict-content-security-policy).
This can be configured as follows:


## Hot reloading during development

If you have enabled `nonce-{{nonce}}` on `style-src`, you will need to disable it in order to allow hot reloading during development.

```ts
export default defineNuxtConfig({
security: {
nonce: true,
headers: {
contentSecurityPolicy: {
'style-src':
process.env.NODE_ENV === 'production'
? [
"'self'", // backwards compatibility for older browsers that don't support strict-dynamic
"'nonce-{{nonce}}'",
]
: // In dev mode, we allow unsafe-inline so that hot reloading keeps working
["'self'", "'unsafe-inline'"],
'script-src': [
"'self'", // fallback value for older browsers, automatically removed if `strict-dynamic` is supported.
"'nonce-{{nonce}}'",
"'strict-dynamic'"
],
'script-src-attr': [
"'self'", // fallback value for older browsers, automatically removed if `strict-dynamic` is supported.
"'nonce-{{nonce}}'",
"'strict-dynamic'"
]
'style-src': process.env.NODE_ENV = 'development' ?
["'self'", "'unsafe-inline'"] :
["'self'", "'unsafe-inline'", "nonce-{{nonce}}"]
}
}
}
})
```

This will add a `nonce` attribute to all `<script>`, `<link>` and `<style>` tags in your application.
Note that to allow hot reloading during development, we conditionally add `'unsafe-inline'` to the `style-src` value.

## Per-route configuration

The `nonce` value is generated per request and is added to the CSP header. This behaviour can be tweaked on a route level by using the `routeRules` option:

Expand All @@ -202,26 +298,69 @@ export default defineNuxtConfig({
})
```

There are two ways to use `nonce` in your application. Check out both of them and decide which one suits your needs best:

1. **`useHead` composable** - If you are dynamically adding script or link tags in your application using the `useHead` composable, all nonce values will be automatically added.
However, take note that due to [a current bug in unjs/unhead](https://github.com/unjs/unhead/issues/136), you'll need to add a workaround **when using ssr** to prevent double loading and executing of your scripts when using nonce.
## Nonces for SSR

For SSR apps, if you use `'strict-dynamic'` in your `script-src` policy, each of your external scripts will need to be whitelisted by nonce.

Fortunately, Nuxt Security will include the nonce in all your relevant HTML resources by default.

You can easily add your external scripts to your HTML document with the `useHead` composable:

```ts
// workaround unjs/unhead bug for double injection when using nonce
// by setting the mode to 'server'
// see: https://github.com/unjs/unhead/issues/136
useHead({ script: [{ src: 'https://example.com/script.js' }] }, { mode: 'server' })
useHead({
script: [
{ src: 'https://example.com/script.js' }
]
}, {
mode: 'server'
// Set the mode to 'server' if you want to avoid re-loading the script on the client
// Set the mode to 'client' if you want client-side only loading
// see: https://github.com/unjs/unhead/issues/136
}
)
```

If you are unwilling or unable to use `useHead`, you can also directly insert tags into the DOM via `document.createElement()`:

```vue
<script setup>
onMounted(() => {
const script = document.createElement('script')
script.src = 'loader.js'
document.head.appendChild(script)
})
</script>
```

2. **Directly inserting tags into DOM** - If you are unable or unwilling to use `useHead` and are inserting directly into the DOM (e.g. `document.createElement`), you can get the current valid nonce value using the `useNonce` composable:
## Integrity Hashes For SSG

For SSG apps, if you use `'strict-dynamic'` in your `script-src` policy, each of your external scripts will need to carry an [integrity attribute](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity).

This is a mandatory requirement of CSP Level 3.

You can easily add integrity values to your scripts with the `useHead` composable:

```ts
const nonce = useNonce()
useHead({
script: [
{
src: 'https://example.com/script.js',
crossorigin: 'anonymous',
integrity: 'sha384-.....' // Insert the integrity hash here

}
]
})
```

You can then use it with Nuxt Image like following:
If you insert scripts manually in your app, you can also include their integrity attribute manually

```html
<NuxtImg src="https://localhost:8000/api/image/xyz" :nonce="nonce" />
<script src="https://example.com/script.js" integrity="sha384-....." crossorigin="anonymous" />
```
::alert{type="warning"}
⚠ For further information about integrity hashes with SSG, please refer to our [Advanced Section on CSP](/documentation/advanced/strict-csp) for alternatives.
::
51 changes: 50 additions & 1 deletion docs/content/1.documentation/5.advanced/2.faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ Next, you need to configure your img tag to include the `crossorigin` attribute:
ℹ Read more about it [here](https://github.com/Baroshem/nuxt-security/issues/138#issuecomment-1497883915).
::


## Using nonce with CSP for Nuxt Image

Having securely configured images is crucial for modern web applications. Check out how to do it below:
Expand Down Expand Up @@ -201,6 +200,7 @@ To use this middleware, include it in your script:
})
</script>
```

### Using NuxtLink

Alternatively, you can use the `external` attribute on `NuxtLink` to set the navigation to external:
Expand All @@ -214,3 +214,52 @@ Alternatively, you can use the `external` attribute on `NuxtLink` to set the nav
::alert{type="info"}
ℹ Read more about it [here](https://github.com/Baroshem/nuxt-security/issues/228).
::

## Cross-Origin-Resource-Policy in Paypal Checkout

To add Paypal Checkout Button in a Nuxt Project you would need to add below security configuration:

```ts
// nuxt.config.ts

routeRules: {
'/payment': {
headers: {
'Cross-Origin-Embedder-Policy': 'unsafe-none',
'Cross-Origin-Resource-Policy': 'cross-origin',
},
},
},
security: {
headers: {
contentSecurityPolicy: {
'img-src': [
"'self'",
'data:',
'https://paypal.com',
'https://www.paypalobjects.com',
],
},
strictTransportSecurity: 'max-age=0;',
},
},
```

And then, you can load the PayPal script with `useHead` composable:

```ts
useHead({
// Paypal SDK to show Paypal button on Payment Page
script: [
{
src: `https://www.paypal.com/sdk/js?client-id=YOURCLIENTID&components=buttons,marks&currency=USD&disable-funding=card`,
crossorigin: 'anonymous',
},
],
noscript: [{ children: 'JavaScript is required' }],
});
```

::alert{type="info"}
ℹ Read more about it [here](https://github.com/Baroshem/nuxt-security/issues/255#issuecomment-1793476794).
::
Loading

0 comments on commit 75b3999

Please sign in to comment.