Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore/1.3.0 #399

Merged
merged 20 commits into from Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/content/1.documentation/5.advanced/2.faq.md
Expand Up @@ -376,3 +376,26 @@ 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).
::

### Running app with `--host` flag

If you want to expose your app in local network to test it by using other devices you can do so by adding the following configuration:

```ts
security: {
headers: {
crossOriginEmbedderPolicy: process.env.NODE_ENV === 'development' ? 'unsafe-none' : 'require-corp', //https://github.com/Baroshem/nuxt-security/issues/101
strictTransportSecurity: true,
contentSecurityPolicy: {
"upgrade-insecure-requests": process.env.NODE_ENV === 'development' ? false : true // USE ONLY IN DEV MODE
}
},
corsHandler: {
origin:'*'
}
}
```

::alert{type="info"}
ℹ Read more about it [here](https://github.com/Baroshem/nuxt-security/issues/397).
::
16 changes: 8 additions & 8 deletions package.json
Expand Up @@ -51,25 +51,25 @@
},
"packageManager": "yarn@1.22.19",
"dependencies": {
"@nuxt/kit": "^3.8.0",
"@nuxt/kit": "^3.10.3",
"basic-auth": "^2.0.1",
"cheerio": "^1.0.0-rc.12",
"defu": "^6.1.1",
"nuxt-csurf": "^1.3.1",
"nuxt-csurf": "^1.5.1",
"pathe": "^1.0.0",
"unplugin-remove": "^0.1.7",
"unplugin-remove": "^1.0.0",
"xss": "^1.0.14"
},
"devDependencies": {
"@nuxt/eslint-config": "^0.2.0",
"@nuxt/module-builder": "^0.5.2",
"@nuxt/schema": "^3.8.0",
"@nuxt/test-utils": "^3.8.0",
"@nuxt/module-builder": "^0.5.5",
"@nuxt/schema": "^3.10.3",
"@nuxt/test-utils": "^3.11.0",
"@types/node": "^18.18.1",
"eslint": "^8.50.0",
"nuxt": "^3.8.0",
"nuxt": "^3.10.3",
"typescript": "^5.2.2",
"vitest": "^1.0.4"
"vitest": "^1.3.1"
},
"stackblitz": {
"installDependencies": false,
Expand Down
15 changes: 11 additions & 4 deletions playground/app.vue
Expand Up @@ -3,17 +3,24 @@
</template>

<script setup>
const formData = new FormData()
formData.append('test', 'test')
const { data } = useFetch('/api/test', {
method: 'POST',
body: formData
})
// Permissions Policy Example

onMounted(async () => {
navigator.geolocation.getCurrentPosition(
() => {},
(err) => console.error(err.message)
);
)

try {
await navigator.mediaDevices.getUserMedia({ video: true });
await navigator.mediaDevices.getUserMedia({ video: true })
} catch (err) {
console.error(err);
console.error(err)
}
});
})
</script>
7 changes: 4 additions & 3 deletions src/runtime/nitro/plugins/03-subresourceIntegrity.ts
Expand Up @@ -19,10 +19,11 @@ export default defineNitroPlugin((nitroApp) => {
//
// - Conversely, if we are in a standalone SSR server pre-built by nuxi build
// Then we don't have a .nuxt build directory anymore
// But we did save the /integrity directory into the server assets
// But we did save the /integrity directory into the server assets
const prerendering = isPrerendering(event)
const storageBase = prerendering ? 'build' : 'assets'
const sriHashes: Record<string, string> = await useStorage(storageBase).getItem('integrity:sriHashes.json') || {}
const storageBase = prerendering ? 'build' : 'assets'
const sriHashesRaw = await useStorage(storageBase).getItemRaw<Uint8Array>('integrity:sriHashes.json')
const sriHashes: Record<string, string> = sriHashesRaw ? JSON.parse(new TextDecoder().decode(sriHashesRaw)) : {}

// Scan all relevant sections of the NuxtRenderHtmlContext
// Note: integrity can only be set on scripts and on links with rel preload, modulepreload and stylesheet
Expand Down
31 changes: 26 additions & 5 deletions src/runtime/server/middleware/xssValidator.ts
@@ -1,29 +1,50 @@
import { FilterXSS, IFilterXSSOptions } from 'xss'
import { defineEventHandler, createError, getQuery, readBody, getRouteRules } from '#imports'
import {
defineEventHandler,
createError,
getQuery,
readBody,
getRouteRules
} from '#imports'
import { HTTPMethod } from '~/src/module'

export default defineEventHandler(async (event) => {
const { security } = getRouteRules(event)

if (security?.xssValidator) {
const filterOpt: IFilterXSSOptions = { ...security.xssValidator, escapeHtml: undefined }
if (security.xssValidator.escapeHtml === false) { // No html escaping (by default "<" is replaced by "&lt;" and ">" by "&gt;")
const filterOpt: IFilterXSSOptions = {
...security.xssValidator,
escapeHtml: undefined
}
if (security.xssValidator.escapeHtml === false) {
// No html escaping (by default "<" is replaced by "&lt;" and ">" by "&gt;")
filterOpt.escapeHtml = (value: string) => value
}
const xssValidator = new FilterXSS(filterOpt)

if (event.node.req.socket.readyState !== 'readOnly') {
if (security.xssValidator.methods && security.xssValidator.methods.includes(event.node.req.method! as HTTPMethod)) {
if (
security.xssValidator.methods &&
security.xssValidator.methods.includes(
event.node.req.method! as HTTPMethod
)
) {
const valueToFilter =
event.node.req.method === 'GET'
? getQuery(event)
: event.node.req.headers['content-type']?.includes(
'multipart/form-data'
)
? await readMultipartFormData(event)
: await readBody(event)
// Fix for problems when one middleware is returning an error and it is catched in the next
if (valueToFilter && Object.keys(valueToFilter).length) {
if (
valueToFilter.statusMessage &&
valueToFilter.statusMessage !== 'Bad Request'
) { return }
) {
return
}
const stringifiedValue = JSON.stringify(valueToFilter)
const processedValue = xssValidator.process(
JSON.stringify(valueToFilter)
Expand Down
19 changes: 16 additions & 3 deletions test/xssValidator.test.ts
Expand Up @@ -4,20 +4,33 @@ import { setup, fetch } from '@nuxt/test-utils'

describe('[nuxt-security] Cross Site Scripting', async () => {
await setup({
rootDir: fileURLToPath(new URL('./fixtures/xss', import.meta.url)),
rootDir: fileURLToPath(new URL('./fixtures/xss', import.meta.url))
})

it ('should return 400 Bad request when passing a script in query or body', async () => {
it('should return 400 Bad Request when passing a script in query or body', async () => {
const res = await fetch('/?test=<script>alert(1)</script>')

expect(res.status).toBe(400)
expect(res.statusText).toBe('Bad Request')
})

it ('should return 200 OK when passing a script in query or body for certain route', async () => {
it('should return 200 OK when passing a script in query or body for certain route', async () => {
const res = await fetch('/test?text=<script>alert(1)</script>')

expect(res.status).toBe(200)
expect(res.statusText).toBe('OK')
})

it('should return 200 OK when passing formdata properly', async () => {
const formData = new FormData()
formData.append('field1', 'value1')
formData.append('field2', 'value2')

const res = await fetch('/', {
method: 'POST',
body: formData
})
expect(res.status).toBe(200)
expect(res.statusText).toBe('OK')
})
})