Blog/Web-Engineering
Eine Nuxt-App absichern: CSP, Header und Limits mit Nuxt Security
Wie das Modul Nuxt Security in der Praxis wirkt: sichere Standardwerte, Kompromisse des Strict-Modus, Nonce-CSP in SSR und SSG, Routenregeln und Limits.
Balázs Csorba··11 Min. Lesezeit
- Nuxt
- Content Security Policy
- HTTP security headers
- Rate limiting
- Nitro

Das Wichtigste in Kürze
- Nuxt Security ist eine nützliche Standardschicht für Header und Anfragelimits, doch der dokumentierte Funktionsumfang enthält keine Authentifizierung, keine Autorisierung, keinen SSRF-Schutz und keine Web Application Firewall.
- Die Standardwerte halten eine typische App funktionsfähig. Der Strict-Modus ist eine andere Richtlinie; beheben Sie deshalb die Brüche im Report-Only-Modus, bevor Sie erzwingen.
- Nonce-CSP setzt Server-Rendering voraus. Statische Builds nutzen Hashes aus dem Build in einem Meta-Tag, deshalb testen Sie jede Seitenart getrennt.
- Der eingebaute Rate Limiter ist eine Bremse auf Anwendungsebene. Schutz vor Brute-Force-Angriffen gehört auf die Infrastrukturebene.
- Rollen Sie in den Report-Only-Modus aus, prüfen Sie die echten Header in End-to-End-Tests und formulieren Sie Ausnahmen pro Route, statt die ganze Seite zu lockern.
Nuxt Security ist ein Modul, das eine Nuxt-Anwendung um eine Härtungsschicht erweitert. Es setzt Sicherheits-Header der Antwort, darunter eine Content Security Policy (CSP), ergänzt Limits für Größe und Häufigkeit von Anfragen, prüft Eingaben auf Cross-Site-Scripting, übernimmt CORS und entfernt einige Spuren des Frameworks. Stand Oktober 2026 ist die neueste Version 2.6.0 vom 12. Mai 2026, und das Modul funktioniert mit Nuxt 3.x und neueren Versionen einschließlich 4.x. Die Standardwerte sollen eine typische App sicher halten, ohne sie zu brechen. Das ist nützlich, aber nicht die ganze Geschichte: Manche Schutzmaßnahmen hängen davon ab, ob eine Seite auf dem Server gerendert oder als statische Datei erzeugt wird, manche brechen Drittanbieter-Widgets, und der Rate Limiter ist ausdrücklich nicht für komplexe Produktionsanwendungen gedacht.
Dieser Artikel behandelt das Modul als Betriebsschicht und nicht als Funktionsliste. Er erklärt, was die Standardwerte garantieren und was der Strict-Modus ändert, wie sich Nonce-basierte CSP zwischen Server-Rendering und statischer Generierung unterscheidet, wie Regeln pro Route überschrieben werden, warum der eingebaute Rate Limiter eine Bremse und kein Schutz vor Denial of Service ist und wann handgeschriebene Nitro-Middleware die bessere Wahl ist. Alle Zahlen stammen aus der Dokumentation oder dem Quellcode des Moduls, Stand Oktober 2026.
Was deckt Nuxt Security ab, und was lässt es offen?
Nuxt Security deckt die HTTP-Schicht ab: Sicherheits-Header der Antwort, darunter CSP für statische Seiten, Limits für Anfragegröße und Häufigkeit, XSS-Prüfung für GET- und POST-Anfragen, CORS, das Ausblenden des Headers X-Powered-By und das Entfernen der Hilfsfunktionen für Console-Logger. Erlaubte HTTP-Methoden, Basic Auth und CSRF-Schutz sind optional. Die README des Moduls führt diesen Funktionsumfang auf und nennt Nuxt 3.x und neuer, einschließlich 4.x, als unterstützt.
Die Lücken sind genauso wichtig wie die Liste. Der dokumentierte Funktionsumfang enthält keinen SSRF-Schutz, keine Authentifizierung oder Autorisierung und keine Web Application Firewall. Wenn Ihre App Adressen abruft, die Nutzer angeben, oder private Daten hinter einer Anmeldung bereitstellt, übernimmt das Modul diese Arbeit nicht. Betrachten Sie den XSS-Validator als einen Filter für eingehende Anfragedaten, der auf js-xss basiert. Die Maskierung der Ausgabe in Ihren Komponenten bleibt Ihre Aufgabe.
Was garantieren die Standardwerte, und was ändert der Strict-Modus?
Die Standardwerte ergeben eine Richtlinie, die mit einer typischen App funktioniert. Skripte dürfen von der eigenen Herkunft und aus https:-Quellen kommen, Styles dürfen Inline-CSS verwenden, und die Skriptrichtlinie nennt einen Nonce zusammen mit 'strict-dynamic'. Browser, die Nonces verstehen, ignorieren den Rückfall unsafe-inline, sobald ein Nonce vorhanden ist. Die übrigen Standardwerte sind nonce: true, sri: true, ein Cross-Origin-Embedder-Policy-Wert credentialless in Produktion, HSTS mit einer max-age von 15.552.000 Sekunden und includeSubdomains, no-referrer als Referrer-Policy und SAMEORIGIN für X-Frame-Options. Rate Limits, Größenbegrenzungen und CORS sind aktiv, Basic Auth und CSRF sind aus.
Der Strict-Modus ist eine andere Richtlinie und keine strengere Fassung derselben Werte. strict: true setzt default-src auf 'none', entfernt unsafe-inline für Skripte, erlaubt Styles nur mit dem Nonce, begrenzt Frames und Worker auf 'self', stellt COEP auf require-corp um, hebt HSTS auf ein Jahr mit Preload an, setzt X-Frame-Options auf DENY und verweigert standardmäßig jede Permissions-Policy-Funktion. Die Dokumentation beschreibt einen schrittweisen Weg: mit den Standardwerten beginnen, die Brüche beheben und dann auf Strict umstellen.
| Funktion | Standard | Pro Route | Vorbehalt, der zuerst auftritt |
|---|---|---|---|
| CSP für SSR | Nonce plus strict-dynamic | ja, unter security | Nonce wird bei statischen Builds ignoriert |
| CSP für SSG | Hashes aus dem Build in einem Meta-Tag | pro Route nicht dokumentiert | Inline-Code muss gehasht werden |
| Rate Limiter | 150 requests per 300,000 ms per IP | ja, unter security | LRU-Zustand liegt in jedem Prozess |
| Anfragegröße | 2,000,000 bytes, 8,000,000 for uploads | ja, unter security | 413 bei zu großen Anfragen |
| XSS-Validator | aktiv für GET und POST | ja, unter security | 400, wenn unsicherer Code vorkommt |
| CORS | Ursprung ist die Server-URL | ja, unter security | useRegExp behandelt Ursprünge als Muster |
| CSRF | aus | Ausnahme über csurf: false | Aktivierung über nuxt-csurf |
| Subresource Integrity | an, Hashes beim Build | pro Route nicht dokumentiert | Host-seitige Änderungen brechen die Hashes |
| COEP | credentialless in Produktion | ja, unter security | Popup-Anmeldungen brauchen Lockerung |
Wie funktioniert Nonce-CSP bei SSR, SSG und Hybrid-Apps?
Beim Server-Rendering erzeugt das Modul für jede Antwort einen Nonce, trägt ihn in den CSP-Header ein und ergänzt ihn an den Script-, Link- und Style-Elementen des gerenderten HTML. Bei statischer Generierung funktioniert das nicht, weil eine Datei alle Besucher bedient. Das Modul berechnet Hashes beim Build und liefert die Richtlinie in einem <meta http-equiv>-Tag aus. Die Dokumentation stellt klar, dass die Option nonce und die Platzhalter 'nonce-{{nonce}}' ignoriert werden, wenn Sie mit nuxi generate für SSG bauen.
Das Umschreiben passiert in der Render-Pipeline, deshalb muss Anwendungscode selten etwas über Nonces wissen. Das Modul verarbeitet Inline-Elemente ebenso wie externe Ressourcen. Braucht Ihr eigener Code einen Nonce, etwa für ein Inline-Skript, das er erzeugt, liefert useNonce() den Wert aus dem Serverkontext:
<script setup lang="ts">
const nonce = useNonce() // set during SSR; empty on a static build
useHead({
script: [{ children: 'window.dataLayer = window.dataLayer || []', nonce }],
})
</script>Ein statischer Build hat keine Anfrage, deshalb ist der Wert dort leer, und Inline-Code, der auf einer vorgerenderten Seite laufen muss, braucht stattdessen einen Hash. Der Hybridfall sorgt für die meiste Verwirrung. Eine Hybrid-App mischt serverseitig gerenderte und vorgerenderte Routen, daher können beide Antwortarten unterschiedliche Richtlinien tragen, und eine Header-Prüfung auf einem Pfad sagt nichts über den anderen. Prüfen Sie beide Seitenarten.
Drei Brüche kehren immer wieder. Drittanbieter-Code, der zur Laufzeit Script-Tags erzeugt, liefert oft Elemente, die die Richtlinie nicht kennt. Die FAQ des Moduls sagt, dass die Inline-Fehlerbehandlung von <NuxtImg> und <NuxtPicture> bei einer reinen Nonce-Richtlinie in SSR kollidiert. Und Hosting-Anbieter, die Ihr Bundle nach dem Build nachbearbeiten, machen die Subresource-Integrity-Hashes ungültig.
Wie härten Sie einzelne Routen ab?
Überschreiben Sie das Modul pro Pfad unter routeRules im Feld security. Jede Middleware und jeder Header lässt sich dort setzen, sodass ein teurer API-Pfad ein engeres Limit haben kann als Ihre Marketingseiten, ohne einen eigenen Server.
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['nuxt-security'],
security: {
strict: true,
contentSecurityPolicyReportOnly: true,
rateLimiter: { tokensPerInterval: 150, interval: 300000 },
},
routeRules: {
// illustrative values: a tighter limit for one expensive endpoint
'/api/generate/**': { security: { rateLimiter: { tokensPerInterval: 30, interval: 60000 } } },
},
})Zwei Details stolpern Menschen immer wieder. Erstens gehören Sicherheits-Header nur in das Feld security. Die Dokumentation rät davon ab, sie über die native Option routeRules.headers zu setzen, und wenn beide denselben Header definieren, gewinnt das Feld security. Zweitens sollten manche Werte gar nicht im Repository liegen. Der Nitro-Hook nuxt-security:routeRules läuft beim Neustart des Servers und kann den gesamten Regelsatz ändern, was zu einer Liste erlaubter connect-src-Ursprünge aus einem Secret Manager passt. Sein Gegenstück zur Build-Zeit, nuxt-security:prerenderedHeaders, deckt statisches Hosting ab.
Hier wächst die Richtlinie einer App mit LLM-Funktionen. Gestreamte Antworten brauchen ihren Ursprung in connect-src, und jedes Anbieter-Widget braucht einen Eintrag in script-src. Das Feld security pro Route hält jede Ausnahme dort, wo sie gebraucht wird, statt die ganze Seite zu lockern. Das gleiche Streaming-Setup beschreibt der Artikel zu Nuxt-LLM-Streaming, und genau bei solchen Endpunkten schleicht sich gern eine zu breite Ausnahme ein.
Was schützen die Limits für Größe und Häufigkeit tatsächlich?
Sie bremsen einen einzelnen Client, der zu viele Anfragen oder übergroße Inhalte an eine laufende Instanz schickt. Der Standard erlaubt 150 Anfragen pro IP innerhalb eines Fensters von 300.000 ms (fünf Minuten), verfolgt im LRU-Cache, und beantwortet Anfragen über dem Limit mit 429 Too Many Requests. Der Größenbegrenzer erzwingt 2.000.000 Bytes bei GET-, POST- und DELETE-Anfragen und beantwortet größere mit 413 Payload Too Large; Upload-Routen erhalten 8.000.000 Bytes.
Die Dokumentation des Moduls nennt die Grenze dieses Ansatzes. Der eingebaute Rate Limiter „ist nicht für komplexe Produktionsanwendungen geeignet“, er soll einfacheren Anwendungen bei Brute-Force-Angriffen helfen, und für den Produktivbetrieb „sollten Sie Lösungen auf der Infrastrukturebene verwenden, nicht auf der Anwendungsebene“. Ich lese das als Arbeitsregel: Der eingebaute Limiter ist eine Bremse, kein Schutz vor Denial of Service. Ein CDN, ein Load Balancer oder eine Web Application Firewall sieht den Verkehr vor dem Node-Prozess und kann ihn abweisen, zu Kosten, die Ihre Anwendung nie trägt. Dieser Limiter ist der dünne Teil der Verteidigung auf Anwendungsebene, den der Artikel zu Denial of Wallet für einen teuren Endpunkt beschreibt.
Daraus folgen zwei praktische Punkte. Der LRU-Standard lebt in einem einzigen Prozess; bei mehreren Instanzen oder bei Serverless-Funktionen, die keinen Speicher teilen, zählt jede Instanz für sich, und das wirksame Limit vervielfacht sich. Das ist meine Lesart des Entwurfs und keine zitierte Aussage; ein Test mit zwei Instanzen würde es bestätigen. Der unstorage-Treiber kann die Zähler in gemeinsamen Speicher verlagern, etwa den in der Dokumentation gezeigten Treiber vercelKv. Zweitens steht hinter einem Proxy die Proxy-Adresse als Client-Adresse, solange Sie nicht ipHeader setzen, etwa auf cf-connecting-ip. Setzen Sie ihn nur, wenn die App ausschließlich über diesen Proxy erreichbar ist, sonst kann ein Client den Header selbst schicken.
Wie verhalten sich CORS und CSRF bei API-Routen?
CORS ist global aktiviert und erlaubt standardmäßig die eigene Server-URL als Ursprung. CSRF-Schutz ist aus, bis Sie ihn einschalten; er kommt über eine Integration mit nuxt-csurf. Der CORS-Handler baut auf der eingebauten CORS-Funktionalität von H3 auf. Sie können methods, allowHeaders, exposeHeaders, credentials und maxAge setzen, und der Status für Preflight-Anfragen ist standardmäßig 204. Mit aktiviertem useRegExp wird jede Ursprungszeichenkette in einen Regulären Ausdruck ohne Beachtung der Groß- und Kleinschreibung übersetzt. Das ist praktisch für Preview-Deployments und gefährlich, wenn ein Muster zu weit ist; halten Sie die Liste deshalb wo immer möglich explizit. Die Dokumentation des CORS-Handlers zeigt alle Optionen.
CSRF ist eine Option, die Sie aktivieren. Setzen Sie csrf: true, nehmen Sie einzelne Routen über die eigene Routenregel csurf: false aus und verwenden Sie auf dem Client die automatisch importierten Composables useCsrfFetch und useCsrf(). Die Dokumentation zeigt außerdem, wie der Token für einen tRPC-Client im Header csrf-token gesendet wird. Basic Auth ist ebenfalls optional. Das Modul verschiebt seine Zugangsdaten in runtimeConfig.private.basicAuth, sodass sie nicht im Client-Bundle landen. Dort gehören sie hin, aber es bleibt ein gemeinsames Passwort, deshalb würde ich es nicht als Anmeldung für ein öffentliches Produkt verwenden.
Wie rollen Sie die Header-Schicht aus und testen sie?
Rollen Sie eine Richtlinie im Report-Only-Modus aus, lesen Sie die Berichte und erzwingen Sie sie erst dann. Setzen Sie contentSecurityPolicyReportOnly: true, dann trägt die Antwort Content-Security-Policy-Report-Only statt des erzwingenden Headers. Die CSP-Optionen akzeptieren report-uri und report-to, global und pro Route. Die Unterstützung für Report-Only kam mit Version 2.6.0, die außerdem die Berechnung der CSP-Hashes geändert hat: Sie erfolgt nun aus dem endgültigen HTML im Render-Hook. Lesen Sie diese Release-Notizen, bevor Sie eine statische Seite aktualisieren.
Für eine einmalige Bewertung einer veröffentlichten Seite prüft das HTTP Observatory von MDN die Header schnell. Es bewertet, was es sieht, kann aber nicht sagen, ob Ihre Seiten noch funktionieren; es ergänzt also Tests im Browser, ersetzt sie aber nicht. Die Prüfung, die Regressionen fängt, ist eine Assertion in einem End-to-End-Test. Die Response-Klasse von Playwright liefert die Antwort-Header, sodass ein Test fehlschlagen kann, wenn ein Deployment die Richtlinie entfernt:
import { test, expect } from '@playwright/test'
test('the home page sends the security headers', async ({ page }) => {
const response = await page.goto('/')
const headers = response!.headers()
expect(headers['content-security-policy'] ?? headers['content-security-policy-report-only']).toContain("script-src 'self'")
expect(headers['strict-transport-security']).toContain('max-age=')
})Führen Sie die Prüfung gegen eine serverseitig gerenderte und eine vorgerenderte Seite aus, denn sie erzeugen unterschiedliche Richtlinien. Dasselbe Playwright-Setup, das ich bereits über Playwright MCP nutze, kann sie bei jedem Deployment laufen lassen, sodass eine Header-Regression an derselben Stelle auftaucht wie eine kaputte Seite. Dieselbe Gewohnheit, vor der Entscheidung zu messen, wende ich auch im Artikel zu Core Web Vitals an, nur ist hier die Messgröße ein Antwort-Header.
Wann ist handgeschriebene Nitro-Middleware die bessere Wahl?
Nutzen Sie das Modul, wenn Ihnen Nonce-Umschreibung, Hashing und Header-Sätze gepflegt abgenommen werden sollen. Schreiben Sie server/middleware selbst, wenn Sie jede Antwort exakt steuern müssen und keine Konvention umgehen wollen. Das Modul liefert Dinge, die mühsam sauber zu bauen sind. Das Einfügen des Nonce passiert in der Render-Pipeline, erreicht also auch Elemente, die das Framework erzeugt. Hashes für statische Builds stammen aus dem endgültigen HTML. Die Header-Sätze unterscheiden sich nach Ressourcentyp, mit einer vollständigen Richtlinie für HTML und einem kleineren Satz für andere Assets, und Regeln pro Route werden mit Ihrer globalen Konfiguration zusammengeführt. Das sind die Teile, die ich nicht neu schreiben würde.
Handgeschriebene Middleware gewinnt an anderer Stelle. Ein paar Header und eine kleine Allowlist in server/middleware sind leicht zu lesen, brauchen keine Modulkonventionen und fügen keine Abhängigkeit hinzu. Wenn Sie nur HSTS, eine Referrer-Policy und Frame-Optionen brauchen, schreiben Sie diese drei Header selbst.
Nonce-CSP von Hand nachzubauen ist der schlechteste Ausgangspunkt. Der Nonce muss jedes Script, jeden Link und jeden Style erreichen, den das Rendering ausgibt, derselbe Wert muss im Header stehen, und statische Builds brauchen einen ganz anderen Mechanismus. Fehler zeigen sich nicht in einem Unit-Test. Sie zeigen sich als leere Seite in einem einzigen Browser. Meine Empfehlung: Nehmen Sie das Modul für die Richtlinie und schreiben Sie jede Ausnahme selbst.
Wie sieht eine Checkliste für den Produktivbetrieb aus?
Bevor Sie eine Richtlinie in einer Produktivanwendung erzwingen, würde ich diese Punkte in dieser Reihenfolge abarbeiten:
- Betreiben Sie die Standardwerte auf jeder Seitenart und beheben Sie, was die Browser-Konsole als Verletzung meldet.
- Aktivieren Sie
contentSecurityPolicyReportOnlymit einem Endpunkt fürreport-urioderreport-tound lesen Sie die Berichte einen vollen Release-Zyklus lang. - Prüfen Sie, welche Routen serverseitig gerendert und welche vorgerendert sind, und kontrollieren Sie das gebaute HTML auf den Nonce oder das gehashte Meta-Tag.
- Laden Sie Drittanbieter-Skripte mit
useScript(Nuxt 3.11 oder neuer) statt Tags von Hand einzufügen. - Tragen Sie externe Bild-Hosts in
img-srcund jeden API-Ursprung inconnect-srcein, einschließlich der LLM-Endpunkte. - Prüfen Sie, dass Ihr Host Bundles nach dem Build nicht umschreibt, sonst schlagen die Integritäts-Hashes fehl.
- Legen Sie den Schutz vor Brute-Force und Traffic-Volumen an den Rand, und behandeln Sie den eingebauten Limiter als zweite Schicht.
- Prüfen Sie die Header in einem End-to-End-Test auf einer serverseitig gerenderten und einer vorgerenderten Seite.
Das Modul ist eine gute Standardschicht, und die verbleibende Arbeit ist dieselbe wie zuvor: wissen, welche Skripte Ihre Seiten laden und welche Ursprünge Ihre App aufruft. Wenn Sie sehen möchten, welche Nuxt-Arbeit hinter diesem Artikel steht, sind meine Vue- und Nuxt-Projekte ein guter Ausgangspunkt.
Quellen
- Nuxt Security README (main branch)
- Nuxt Security default configuration (src/defaultConfig.ts)
- Nuxt Security docs: Content Security Policy
- Nuxt Security docs: rate limiter
- Nuxt Security docs: CORS handler
- Nuxt Security docs: CSRF
- Nuxt Security docs: hooks
- Nuxt Security docs: security headers
- Nuxt Security docs: improving security
- Nuxt Security release v2.6.0 (12 May 2026)
- MDN: HTTP Observatory
- Playwright: Response class
Häufige Fragen
Schützt Nuxt Security vor SSRF?
Der dokumentierte Funktionsumfang enthält keinen SSRF-Schutz, und das Modul ergänzt auch keine Authentifizierung oder Autorisierung. Prüfen Sie ausgehende Adressen in Ihrem eigenen Code und lesen Sie die Release-Notizen, bevor Sie sich auf eine Aussage zu einer bestimmten Version verlassen.
Reicht die Standard-CSP von Nuxt Security für den Produktivbetrieb?
Die Standardwerte sind so angelegt, dass eine typische App nicht bricht, aber sie sind ein Ausgangspunkt. Sie behalten unsafe-inline als Rückfall und erlauben https:-Quellen für Skripte. Prüfen Sie die Richtlinie im fertigen Build, beheben Sie die Brüche und wechseln Sie dann in den Strict-Modus oder zu einer engeren eigenen Richtlinie.
Warum funktioniert Nonce-CSP auf einer statischen Seite nicht?
Ein Nonce wird pro Antwort erzeugt, eine statische Datei ist für jeden Besucher gleich. Nuxt Security ignoriert die Option nonce bei nuxi generate und verwendet stattdessen Hashes aus dem Build, die in einem Meta-Tag ausgeliefert werden.
Stoppt der Rate Limiter Denial-of-Service-Angriffe?
Nein. Die Dokumentation beschreibt ihn als Werkzeug für einfachere Anwendungen und empfiehlt für Brute-Force-Schutz Lösungen auf Infrastrukturebene. Setzen Sie für das Traffic-Volumen ein CDN, einen Load Balancer oder eine Web Application Firewall davor und behalten Sie den Limiter der Anwendung als zweite Schicht.
Wie prüfe ich, dass die Header nach einem Deployment noch da sind?
Schreiben Sie einen End-to-End-Test, der die Antwort-Header einer serverseitig gerenderten und einer vorgerenderten Seite liest und fehlschlägt, wenn Content-Security-Policy oder Strict-Transport-Security fehlt.