Skip to content

Commit f16ae6b

Browse files
authored
Merge pull request #465 from Baroshem/chore/2.0.0-rc.4
fix(headers): add default for connect-src
2 parents 10c6e1d + 38192f3 commit f16ae6b

15 files changed

+76
-109
lines changed

.stackblitz/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"postinstall": "nuxt prepare"
99
},
1010
"devDependencies": {
11-
"nuxt": "3.9.3"
11+
"nuxt": "^3.11.2"
1212
},
1313
"dependencies": {
1414
"nuxt-security": "latest"

docs/content/1.documentation/1.getting-started/2.configuration.md

+33-9
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ security: {
4545
crossOriginEmbedderPolicy: 'require-corp',
4646
contentSecurityPolicy: {
4747
'base-uri': ["'none'"],
48+
'default-src': ["'self'"],
49+
'connect-src': ["'self'", 'https:'],
4850
'font-src': ["'self'", 'https:', 'data:'],
49-
'form-action': ["'self'"],
50-
'frame-ancestors': ["'self'"],
5151
'img-src': ["'self'", 'data:'],
5252
'object-src': ["'none'"],
5353
'script-src-attr': ["'none'"],
@@ -58,21 +58,45 @@ security: {
5858
originAgentCluster: '?1',
5959
referrerPolicy: 'no-referrer',
6060
strictTransportSecurity: {
61-
maxAge: 15552000,
61+
maxAge: 31536000,
6262
includeSubdomains: true
6363
},
6464
xContentTypeOptions: 'nosniff',
6565
xDNSPrefetchControl: 'off',
6666
xDownloadOptions: 'noopen',
67-
xFrameOptions: 'SAMEORIGIN',
67+
xFrameOptions: 'DENY',
6868
xPermittedCrossDomainPolicies: 'none',
6969
xXSSProtection: '0',
7070
permissionsPolicy: {
71-
camera: [],
72-
'display-capture': [],
73-
fullscreen: [],
74-
geolocation: [],
75-
microphone: []
71+
accelerometer: [],
72+
'ambient-light-sensor':[],
73+
autoplay:[],
74+
battery:[],
75+
camera:[],
76+
'display-capture':[],
77+
'document-domain':[],
78+
'encrypted-media':[],
79+
fullscreen:[],
80+
gamepad:[],
81+
geolocation:[],
82+
gyroscope:[],
83+
'layout-animations':['self'],
84+
'legacy-image-formats':['self'],
85+
magnetometer:[],
86+
microphone:[],
87+
midi:[],
88+
'oversized-images':['self'],
89+
payment:[],
90+
'picture-in-picture':[],
91+
'publickey-credentials-get':[],
92+
'speaker-selection':[],
93+
'sync-xhr':['self'],
94+
'unoptimized-images':['self'],
95+
'unsized-media':['self'],
96+
usb:[],
97+
'screen-wake-lock':[],
98+
'web-share':[],
99+
'xr-spatial-tracking':[]
76100
}
77101
},
78102
requestSizeLimiter: {

docs/content/1.documentation/2.headers/1.csp.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ You can also disable this header by `contentSecurityPolicy: false`.
4545
By default, Nuxt Security will set following value for this header:
4646

4747
```http
48-
Content-Security-Policy: base-uri 'none'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src 'self' data:; object-src 'none'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'; script-src 'self' https: 'unsafe-inline' 'strict-dynamic' 'nonce-{{nonce}}'; upgrade-insecure-requests;
48+
Content-Security-Policy: base-uri 'none'; default-src 'self'; connect-src 'self', https:; font-src 'self' https: data:; img-src 'self' data:; object-src 'none'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'; script-src 'self' https: 'unsafe-inline' 'strict-dynamic' 'nonce-{{nonce}}'; upgrade-insecure-requests;
4949
```
5050

5151
## Available values
@@ -160,13 +160,13 @@ export default defineNuxtConfig({
160160
"https:", // For increased security, replace by the specific hosting domain or file name of your external stylesheets
161161
"'unsafe-inline'" // Recommended default for most Nuxt apps
162162
],
163+
'base-uri': ["'none'"],
164+
'default-src': ["'self'"],
165+
'connect-src': ["'self'", 'https:'],
163166
'img-src': ["'self'", "data:"], // Add relevant https://... sources if you load images from external sources
164167
'font-src': ["'self'", "https:", "data:"], // For increased security, replace by the specific sources for fonts
165-
'base-uri': ["'none'"],
166168
'object-src': ["'none'"],
167169
'script-src-attr': ["'none'"],
168-
'form-action': ["'self'"],
169-
'frame-ancestors': ["'self'"],
170170
'upgrade-insecure-requests': true
171171
}
172172
},

docs/content/1.documentation/2.headers/2.permissions-policy.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export default defineNuxtConfig({
5757
By default, Nuxt Security will set following value for this header.
5858

5959
```http
60-
Permissions-Policy: camera=(), display-capture=(), fullscreen=(), geolocation=(), microphone=();
60+
Permissions-Policy: accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), display-capture=(), document-domain=(), encrypted-media=(), fullscreen=(), gamepad=(), geolocation=(), gyroscope=(), layout-animations=(self), legacy-image-formats=(self), magnetometer=(), microphone=(), midi=(), oversized-images=(self), payment=(), picture-in-picture=(), publickey-credentials-get=(), speaker-selection=(), sync-xhr=(self), unoptimized-images=(self), unsized-media=(self), usb=(), screen-wake-lock=(), web-share=(), xr-spatial-tracking=();
6161
```
6262

6363
## Available values

docs/content/1.documentation/5.advanced/3.strict-csp.md

+2-66
Original file line numberDiff line numberDiff line change
@@ -732,70 +732,6 @@ From an application design perspective, it is simpler to use a single Strict CSP
732732
733733
In order to obtain a Strict CSP on Nuxt apps, we need to use `strict-dynamic`. This mode disallows the ability for scripts to insert inline styles, and cancels the ability to whitelist external resources by name. In conjunction with the fact that nonces and hashes disable the `'unsafe-inline'` mode, this leaves us with very few options to customize our CSP policies.
734734
735-
On the other hand, it obliges the developers community to adopt a standardized mindset when thinking about CSP. Less configuration options means less potential loopholes that malicious actors can seek to exploit.
735+
On the other hand, it obliges application developers to adopt a standardized mindset when thinking about CSP. Less configuration options means less potential loopholes that malicious actors can seek to exploit.
736736
737-
With this in mind, we recommend that you implement your Strict CSP policy by checking your configuration against the following template:
738-
739-
```ts
740-
export default defineNuxtConfig({
741-
security: {
742-
nonce: true, // Enables HTML nonce support in SSR mode
743-
ssg: {
744-
meta: true, // Enables CSP as a meta tag in SSG mode
745-
hashScripts: true, // Enables CSP hash support for scripts in SSG mode
746-
hashStyles: false // Disables CSP hash support for styles in SSG mode (recommended)
747-
nitroHeaders: true // Allow Nitro to serve security headers for pre-rendered routes
748-
exportToPresets: true // Export pre-rendered security headers to Nitro presets
749-
},
750-
// You can use nonce and ssg simultaneously
751-
// Nuxt Security will take care of choosing the adequate parameters when you build for either SSR or SSG
752-
headers: {
753-
contentSecurityPolicy: {
754-
'script-src': [
755-
"'self'", // Fallback value, will be ignored by browsers level 3
756-
"https://domain.com/external-script.js", // Fallback value, will be ignored by browsers level 3
757-
"'unsafe-inline'", // Fallback value, will be ignored by browsers level 2 & 3
758-
"'strict-dynamic'", // Strict CSP via 'strict-dynamic', supported by browsers level 3
759-
"'nonce-{{nonce}}'" // Enables CSP nonce support for scripts in SSR mode, supported browsers level 2 & 3
760-
],
761-
'style-src': [
762-
"'self'", // Enables loading of stylesheets hosted on self origin
763-
"https://domain.com/file.css", // Use fully-qualified filenames rather than the https: generic
764-
"https://trusted-domain.com", // Avoid using domain stubs unless you can fully trust them
765-
"'unsafe-inline'" // Recommended default for most Nuxt apps, but make sure 'img-src' is properly set up
766-
//"'nonce-{{nonce}}'" // Disables CSP nonce support, otherwise would cancel 'unsafe-inline'
767-
// You can re-enable if your application does not modify inline styles dynamically
768-
],
769-
"img-src": [
770-
"'self'", // Enables loading of images hosted on self origin
771-
"https://domain.com/img.png", // Use fully-qualified filenames rather than the https: generic
772-
"https://trusted-domain.com", // Avoid using domain stubs unless you can fully trust them
773-
"blob:" // If you use Blob to construct images dynamically from javascript
774-
// Qualifying img-src properly mitigates strongly against 'unsafe-inline' in style-src
775-
],
776-
'font-src': [
777-
"'self'", // Enables loading of fonts hosted on self origin
778-
"https://domain.com/font.woff", // Use fully-qualified filenames rather than the https: generic
779-
"https://trusted-domain.com" // Avoid using domain stubs unless you can fully trust them
780-
],
781-
"worker-src": [
782-
"'self'", // Enables loading service worker from self origin,
783-
"blob:" // If you use PWA, it is likely that the worker will be instantiated from Blob
784-
],
785-
"connect-src": [
786-
"'self'", // Enables fetching from self origin
787-
"https://api.domain.com/service", // Use largest prefix possible on API routes
788-
"wss://api.domain.com/messages" // Add Websocket qualifiers if used
789-
],
790-
"object-src": [
791-
"'none'"
792-
],
793-
"base-uri": [
794-
"'none'"
795-
]
796-
// Do not use default-src
797-
}
798-
}
799-
}
800-
})
801-
```
737+
With this in mind, we recommend that you implement your Strict CSP policy by starting from our [default configuration values](/documentation/getting-started/configuration#default), and modifying only the required values.

docs/nuxt.config.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ export default defineNuxtConfig({
1515
security: {
1616
headers: {
1717
contentSecurityPolicy: {
18-
'img-src': ["'self'", "data:", 'https:'] // Allow https: external images
18+
'img-src': ["'self'", "data:", 'https:'], // Allow https: external images
19+
'connect-src': process.env.NODE_ENV === 'development' ? ["'self'", 'https:', 'ws:'] : ["'self'", 'https:'], // Allow self and image api
20+
'frame-src': ['https://www.youtube-nocookie.com', 'https://stackblitz.com'], // Allow self and youtube and stackblitz iframes
1921
},
20-
crossOriginEmbedderPolicy: 'credentialless' // Allow youtube and stackblitz iframes
22+
crossOriginEmbedderPolicy: 'unsafe-none', // Allow youtube and stackblitz iframes
2123
}
2224
}
2325
})

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "nuxt-security",
3-
"version": "2.0.0-rc.3",
3+
"version": "2.0.0-rc.4",
44
"license": "MIT",
55
"type": "module",
66
"homepage": "https://nuxt-security.vercel.app",

src/defaultConfig.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ export const defaultSecurityConfig = (serverlUrl: string): ModuleOptions => ({
1010
contentSecurityPolicy: {
1111
'base-uri': ["'none'"],
1212
'default-src' : ["'self'"],
13+
'connect-src': ["'self'", 'https:'],
1314
'font-src': ["'self'", 'https:', 'data:'],
14-
'form-action': ["'self'"],
15-
'frame-ancestors': ["'self'"],
1615
'img-src': ["'self'", 'data:'],
1716
'object-src': ["'none'"],
1817
'script-src-attr': ["'none'"],

test/fixtures/ssgHashes/nuxt.config.ts

+5
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ export default defineNuxtConfig({
4242
meta: true,
4343
hashScripts: true,
4444
hashStyles: true
45+
},
46+
headers: {
47+
contentSecurityPolicy: {
48+
'frame-ancestors': ["'self'"]
49+
}
4550
}
4651
}
4752
})

test/fixtures/ssrNonce/nuxt.config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export default defineNuxtConfig({
66
routeRules: {
77
'/renew': {
88
security: {
9+
//@ts-expect-error Purposedly deprecated configuration
910
nonce: { mode: 'check' }
1011
}
1112
},

test/headers.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ describe('[nuxt-security] Headers', async () => {
3737
expect(cspHeaderValue).toBeTruthy()
3838
expect(nonceValue).toBeDefined()
3939
expect(nonceValue).toHaveLength(24)
40-
expect(cspHeaderValue).toBe(`base-uri 'none'; default-src 'self'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src 'self' data:; object-src 'none'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'; script-src 'self' https: 'unsafe-inline' 'strict-dynamic' 'nonce-${nonceValue}'; upgrade-insecure-requests;`)
40+
expect(cspHeaderValue).toBe(`base-uri 'none'; default-src 'self'; connect-src 'self' https:; font-src 'self' https: data:; img-src 'self' data:; object-src 'none'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'; script-src 'self' https: 'unsafe-inline' 'strict-dynamic' 'nonce-${nonceValue}'; upgrade-insecure-requests;`)
4141
})
4242

4343
it('has `cross-origin-embedder-policy` header set with correct default value', async () => {

0 commit comments

Comments
 (0)