diff --git a/src/utils/headers.ts b/src/utils/headers.ts
index c04984ea..2af9c0cd 100644
--- a/src/utils/headers.ts
+++ b/src/utils/headers.ts
@@ -177,6 +177,7 @@ export function getHeadersApplicableToAllResources(headers: SecurityHeaders) {
Object.entries(headers)
.filter(([key]) => appliesToAllResources(key as OptionKey))
.map(([key, value]) => ([getNameFromKey(key as OptionKey), headerStringFromObject(key as OptionKey, value)]))
+ .filter(([, value]) => Boolean(value))
)
return Object.keys(applicableHeaders).length === 0 ? undefined : applicableHeaders
}
diff --git a/test/fixtures/publicAssets/.nuxtrc b/test/fixtures/publicAssets/.nuxtrc
new file mode 100644
index 00000000..3c8c6a11
--- /dev/null
+++ b/test/fixtures/publicAssets/.nuxtrc
@@ -0,0 +1 @@
+imports.autoImport=true
\ No newline at end of file
diff --git a/test/fixtures/publicAssets/app.vue b/test/fixtures/publicAssets/app.vue
new file mode 100644
index 00000000..2b1be090
--- /dev/null
+++ b/test/fixtures/publicAssets/app.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/test/fixtures/publicAssets/nuxt.config.ts b/test/fixtures/publicAssets/nuxt.config.ts
new file mode 100644
index 00000000..0271bed5
--- /dev/null
+++ b/test/fixtures/publicAssets/nuxt.config.ts
@@ -0,0 +1,42 @@
+export default defineNuxtConfig({
+ modules: [
+ '../../../src/module'
+ ],
+ routeRules: {
+ '/test/**': {
+ security: {
+ headers: {
+ referrerPolicy: 'no-referrer',
+ strictTransportSecurity: {
+ maxAge: 15552000,
+ includeSubdomains: true,
+ },
+ xContentTypeOptions: 'nosniff',
+ xDownloadOptions: 'noopen',
+ xFrameOptions: 'SAMEORIGIN',
+ xPermittedCrossDomainPolicies: 'none',
+ xXSSProtection: '0',
+ }
+ }
+ }
+ },
+ security: {
+ headers: {
+ referrerPolicy: false,
+ strictTransportSecurity: false,
+ xContentTypeOptions: false,
+ xDownloadOptions: false,
+ xFrameOptions: false,
+ xPermittedCrossDomainPolicies: false,
+ xXSSProtection: false,
+ contentSecurityPolicy: {
+ 'script-src': [
+ "'self'",
+ 'https:',
+ "'unsafe-inline'",
+ "'strict-dynamic'"
+ ]
+ }
+ }
+ }
+})
diff --git a/test/fixtures/publicAssets/package.json b/test/fixtures/publicAssets/package.json
new file mode 100644
index 00000000..decd4334
--- /dev/null
+++ b/test/fixtures/publicAssets/package.json
@@ -0,0 +1,5 @@
+{
+ "private": true,
+ "name": "basic",
+ "type": "module"
+}
diff --git a/test/fixtures/publicAssets/pages/index.vue b/test/fixtures/publicAssets/pages/index.vue
new file mode 100644
index 00000000..8371b274
--- /dev/null
+++ b/test/fixtures/publicAssets/pages/index.vue
@@ -0,0 +1,3 @@
+
+ basic
+
diff --git a/test/fixtures/publicAssets/public/icon.png b/test/fixtures/publicAssets/public/icon.png
new file mode 100644
index 00000000..dbbf391c
Binary files /dev/null and b/test/fixtures/publicAssets/public/icon.png differ
diff --git a/test/fixtures/publicAssets/public/test/icon.png b/test/fixtures/publicAssets/public/test/icon.png
new file mode 100644
index 00000000..dbbf391c
Binary files /dev/null and b/test/fixtures/publicAssets/public/test/icon.png differ
diff --git a/test/publicAssets.test.ts b/test/publicAssets.test.ts
new file mode 100644
index 00000000..9bfe2c70
--- /dev/null
+++ b/test/publicAssets.test.ts
@@ -0,0 +1,53 @@
+import { describe, it, expect } from 'vitest'
+import { fileURLToPath } from 'node:url'
+import { setup, fetch } from '@nuxt/test-utils'
+
+describe('[nuxt-security] Public Assets', async () => {
+ await setup({
+ rootDir: fileURLToPath(new URL('./fixtures/publicAssets', import.meta.url)),
+ })
+
+ it('does not set all-resources security headers when disabled in config', async () => {
+ const { headers } = await fetch('/icon.png')
+ expect(headers).toBeDefined()
+
+ // Security headers that are always set on all resources
+ const rp = headers.get('referrer-policy')
+ const sts = headers.get('strict-transport-security')
+ const xcto = headers.get('x-content-type-options')
+ const xdo = headers.get('x-download-options')
+ const xfo = headers.get('x-frame-options')
+ const xpcdp = headers.get('x-permitted-cross-domain-policies')
+ const xxp = headers.get('x-xss-protection')
+
+ expect(rp).toBeNull()
+ expect(sts).toBeNull()
+ expect(xcto).toBeNull()
+ expect(xdo).toBeNull()
+ expect(xfo).toBeNull()
+ expect(xpcdp).toBeNull()
+ expect(xxp).toBeNull()
+ })
+
+ it('sets security headers on routes when specified in routeRules', async () => {
+ const { headers } = await fetch('/test')
+ expect(headers).toBeDefined()
+
+ // Security headers that are always set on all resources
+ const rp = headers.get('referrer-policy')
+ const sts = headers.get('strict-transport-security')
+ const xcto = headers.get('x-content-type-options')
+ const xdo = headers.get('x-download-options')
+ const xfo = headers.get('x-frame-options')
+ const xpcdp = headers.get('x-permitted-cross-domain-policies')
+ const xxp = headers.get('x-xss-protection')
+
+ expect(rp).toBe('no-referrer')
+ expect(sts).toBe('max-age=15552000; includeSubDomains;')
+ expect(xcto).toBe('nosniff')
+ expect(xdo).toBe('noopen')
+ expect(xfo).toBe('SAMEORIGIN')
+ expect(xpcdp).toBe('none')
+ expect(xxp).toBe('0')
+ })
+})