Skip to content

Commit

Permalink
Merge remote-tracking branch 'Baroshem/chore/1.0.0-rc.4' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
dargmuesli committed Nov 14, 2023
2 parents 62ad0ba + f75159d commit f9d20b4
Show file tree
Hide file tree
Showing 38 changed files with 1,642 additions and 77 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
- '**-rc.**'
- 'renovate/**'
pull_request:
workflow_dispatch:

jobs:
ci:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ interface ModuleOptions {
nonce: boolean;
removeLoggers?: RemoveOptions | false;
ssg?: Ssg;
sri: boolean;
}
```

Expand Down Expand Up @@ -114,8 +115,10 @@ security: {
exclude: [/node_modules/, /\.git/]
},
ssg: {
hashScripts: true
}
hashScripts: true,
hashStyles: false
},
sri: true
}
```

Expand Down
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.
::
69 changes: 69 additions & 0 deletions docs/content/1.documentation/4.utils/3.subresource-integrity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Subresource Integrity

:badge[Enabled]{type="success"} Ensure that your application bundle has not been manipulated.

---

:ellipsis{right=0px width=75% blur=150px}

Subresource Integrity (SRI) is a security feature that enables the browser to verify that the static assets that your application is loading have not been altered.

Nuxt Security automatically computes the integrity hash of each static asset (scripts, stylesheets, etc.) that are bundled in your Nuxt Application, and then inserts this value in the resulting HTML file.


::alert{type="info"}
ℹ Read more about Subresource Integrity [here](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity).
::

## Options

This feature is enabled globally by default. You can customize it like following:

```js{}[nuxt.config.ts]
export default defineNuxtConfig({
// Global
security: {
sri: true
}
})
```

You can disable the feature globally by setting `sri: false`.

## Usage

Subresource Integrity is used for two important security features of your application:

**1. SRI ensures that the assets that _you_ included in your build have not been altered.**

When you build your Nuxt application and deliver it to your users, a significant number of critical components are included in your final bundle.

These components are mostly scripts containing Javascript code (files such as `/_nuxt/entry.b8aef440d.js`), stylesheets, etc. An attacker may try to compromise your application by modifying these files.

Nuxt Security calculates the hash of each of these files _at build time_, therefore guaranteeing that the files that are loaded by your users are exactly the ones that you included in your bundle.

Arguably, if you host your static assets yourself, the risk that these files are modified by a malicious actor without your authorization can be rated as low.

However:

- If you host your application on a public CDN, that CDN could become the target of an attack.
- Even if you host your application on a private hosting service, you should be aware that most hosting providers use elaborate caching strategies to accelerate the delivery of your files (e.g. via edge CDN replication).
- In any case, your own account (or the account of one of the members of your organization) might become compromised.

For these reasons, most modern web applications rely on SRI to reduce their attack surface.

::alert{type="success"}
SRI is supported by all modern browsers: [caniuse](https://caniuse.com/subresource-integrity)
::

**2. SRI is a critical component of Content Security Policy (CSP) in SSG mode.**

For more information on the relationship between Subresource Integrity and a Strict CSP, please read our [Advanced Section on Integrity Hashes for CSP](/documentation/advanced/strict-csp/#ssg-mode)

If you use CSP on a statically-generated application, you will need to enable SRI by setting `sri: true`.

::alert{type="warning"}
Subresource Integrity hashes can only be inserted on `<script>` and `<link>` elements generated as part of the server bundle.
<br>
To protect your Nuxt application after client-side hydration, you must deploy a Strict CSP.
::
Loading

0 comments on commit f9d20b4

Please sign in to comment.