XSS in Nuxt Applications
Nuxt combines Vue's template system with server-side rendering, creating XSS risks from both v-html directives and server-rendered content. useFetch and useAsyncData composables can introduce unsanitized data into the render pipeline if user content flows through them into v-html expressions.
Scan Your Nuxt AppHow XSS Manifests in Nuxt
Nuxt inherits Vue's v-html vulnerability, but adds SSR-specific risks. Data fetched with useFetch() or useAsyncData() on the server is serialized into the HTML payload. If this data contains user HTML rendered via v-html, the XSS payload executes on every page load before Vue hydrates. Nuxt server routes (server/api/) that return HTML responses are vulnerable when they include user data without encoding. The useHead() composable can be exploited if user data flows into meta tags or script content. Nuxt plugins that modify the DOM or inject HTML during SSR can also introduce XSS if they handle user data.
Real-World Impact
A Nuxt-based content platform used v-html to render articles fetched via useAsyncData. Because the content was server-rendered, the XSS payload was embedded in the initial HTML response, executing before any client-side protections loaded. The attacker used this to install a service worker that persisted even after the XSS was patched.
Step-by-Step Fix
Install nuxt-security module
The nuxt-security module adds CSP headers, rate limiting, and other security features automatically.
# Install
npm install nuxt-security
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-security'],
security: {
headers: {
contentSecurityPolicy: {
'default-src': ["'self'"],
'script-src': ["'self'", "'nonce-{nonce}'"],
},
},
},
});Create a sanitization composable
Build a reusable composable for sanitizing HTML throughout your Nuxt app.
// composables/useSanitize.ts
import DOMPurify from 'isomorphic-dompurify';
export function useSanitize() {
function sanitizeHtml(html: string): string {
return DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'ol', 'li'],
ALLOWED_ATTR: ['href', 'target', 'rel'],
});
}
return { sanitizeHtml };
}
// In component
<template>
<div v-html="cleanContent" />
</template>
<script setup>
const { sanitizeHtml } = useSanitize();
const { data } = await useFetch('/api/content');
const cleanContent = computed(() => sanitizeHtml(data.value?.html || ''));
</script>Secure server routes
Encode user data in server API routes that return HTML.
// server/api/preview.get.ts
import { escape } from 'html-escaper';
export default defineEventHandler((event) => {
const query = getQuery(event);
const title = escape(String(query.title || ''));
return `<h1>${title}</h1>`;
});Sanitize useHead data
Ensure user data in meta tags is properly encoded.
<script setup>
const { data } = await useFetch(`/api/pages/${slug}`);
useHead({
title: data.value?.title?.replace(/[<>"']/g, '') || 'Page',
meta: [
{
name: 'description',
content: data.value?.description?.replace(/[<>"']/g, '') || '',
},
],
});
</script>Prevention Best Practices
1. Sanitize all content before using v-html, especially data from useFetch/useAsyncData. 2. Use nuxt-security module for automatic security header configuration. 3. Validate data in server routes before returning HTML responses. 4. Sanitize user data used in useHead() composable. 5. Create a sanitization plugin that runs during SSR.
How to Test
1. Search for v-html directives: grep -r "v-html" --include="*.vue" 2. Trace useFetch/useAsyncData data flows to v-html. 3. Test with <img src=x onerror=alert(1)> payloads in content fields. 4. Check server/api/ routes for unescaped user data in HTML responses. 5. Use Vibe App Scanner to automatically detect XSS patterns in your Nuxt application.
Frequently Asked Questions
Does Nuxt add XSS risks beyond Vue?
Yes. Nuxt's server-side rendering means XSS payloads in v-html execute in the initial HTML before Vue hydrates, making them more severe. Data fetched with useFetch/useAsyncData is serialized into the page, and server routes can return vulnerable HTML responses.
Does the nuxt-security module prevent all XSS?
No. nuxt-security adds CSP headers and other security measures, but CSP alone cannot prevent all XSS. You still need to sanitize content used with v-html and validate user input. nuxt-security is a defense-in-depth layer, not a complete solution.
How do I use nonce-based CSP with Nuxt?
The nuxt-security module supports nonce-based CSP out of the box. Configure it in nuxt.config.ts and use the useNonce() composable to get the nonce value for inline scripts. The module automatically adds nonces to script tags during SSR.
Related Security Resources
Is Your Nuxt App Vulnerable to XSS?
VAS automatically scans for xss vulnerabilities in Nuxt applications and provides step-by-step remediation guidance with code examples.
Scans from $5, results in minutes. Get actionable fixes tailored to your Nuxt stack.