nuxt-security

repository·main·Indexed 21 days ago

https://github.com/baroshem/nuxt-security

A security module for Nuxt 3.X and 4.X applications that implements OWASP security patterns using HTTP headers and server-side middleware. Key features include Content Security Policy (CSP) support, request size and rate limiters, XSS validation, CORS configuration, and the ability to hide the X-Powered-By header. It supports global configuration in nuxt.config.ts, per-route settings via routeRules, and dynamic runtime modifications through the nuxt-security:routeRules Nitro plugin hook.

Tokens
41.1K
Snippets
145
Records
174
Agent score
75%

What's inside nuxt-security

  1. Overview of nuxt-security features

    main

    Nuxt Security automatically configures your application to follow OWASP security patterns using HTTP Headers and Middleware. Key features include:

    • Security response headers: Includes Content Security Policy (CSP) support for SSG apps.
    • Request Size & Rate Limiters: Protects against volumetric attacks.
    • XSS Validation: Cross Site Scripting validation.
    • CORS support: Cross-Origin Resource Sharing configuration.
    • Header & Logger cleanup: Hides the X-Powered-By header and removes console loggers/utils.
    • Optional protections: Allowed HTTP Methods, Basic Auth, and CSRF protection.
  2. Understand how Nuxt Security applies headers to different resource types

    main

    Nuxt Security distinguishes between two types of resources when applying security headers:

    1. HTML resources: These are pages rendered by your application (e.g., .vue files). Nuxt Security delivers the full set of configured security headers to these resources. You can further customize these via the nuxt-security:routeRules runtime hook.

    2. Other resources: These are non-HTML assets like images, files, or API resources. Nuxt Security only delivers a restricted subset of headers relevant to these assets. Note that the nuxt-security:routeRules hook does not modify headers for these resources.

    To ensure maximum security, always use the Nuxt Security configuration for security-specific headers rather than native Nuxt header rules.

  3. Implement Strict CSP in SSR mode using nonces

    main

    For Server-Side Rendering (SSR) applications, Nuxt Security implements Strict CSP using nonces. A unique, cryptographically-generated nonce is created for every request.

    To enable this:

    1. Set security.nonce to true. This tells the module to inject the nonce into all <script>, <link>, and <style> tags.
    2. Use the "'nonce-{{nonce}}'" placeholder in your contentSecurityPolicy configuration to govern specific policies.
    export default defineNuxtConfig({
      security: {
        nonce: true, // Enables HTML nonce support in SSR mode
        headers: {
          contentSecurityPolicy: {
            'script-src': [
              "'strict-dynamic'",
              "'nonce-{{nonce}}'"
            ]
          }
        },
      },
    })
  4. Distinguish between Inline and External resources for CSP

    main

    When configuring CSP, it is critical to understand how the browser treats different types of elements:

    External Resources

    These are elements loaded from a server via a URL.

    • Examples: <img src="https://example.com/image.png">, <script src="/_nuxt/entry.js" />, or <link rel="stylesheet" href="https://cdn.com/style.css" />.
    • CSP Defense: These are managed via whitelisting. If you define script-src https://example.com, any script from a different domain will be blocked.

    Inline Elements

    These are elements directly embedded within the HTML document.

    • Examples: <script>console.log('Hello')</script> or <style>h1 { color: blue }</style>.
    • CSP Defense: By default, CSP forbids all inline elements to prevent XSS (Cross-Site Scripting) attacks. Because Nuxt dynamically inserts many inline elements, you must use specific mechanisms like nonces (for SSR) or hashes (for SSG) to authorize them, rather than simply using the unsafe-inline directive.
    <!-- An inlined script -->
    <script>console.log('Hello World')></script>
    
    <!-- An inlined style -->
    <style>h1 { color: blue }</style>
    
    <!-- An external resource -->
    <script src="/_nuxt/entry.065a09b.js" />
  5. Understand CSP delivery mechanisms in Nuxt Security

    main

    Nuxt Security delivers Content Security Policy (CSP) using two different methods depending on your application's rendering mode. This ensures the most secure delivery possible for your specific architecture.

    SSR (Server-Side Rendering)

    In SSR mode, CSP is delivered via HTTP headers. Because a Nitro server is running, Nuxt Security can modify the headers on the fly for every request.

    SSG (Static Site Generation)

    In SSG mode, there is no running server to manage headers. By default, Nuxt Security uses the HTML <meta http-equiv> tag inside the document. This is a fallback mechanism because static files are served by external servers (like CDNs) that Nuxt cannot control.

    Hybrid Rendering

    For pre-rendered pages in a Hybrid application, CSP is delivered via HTTP headers in addition to the <meta> tag. This is enabled by default via the ssg: nitroHeaders option.

  6. How route rule merging works for nested routes

    main

    Nuxt Security recursively merges security options when you define nested or overlapping routeRules. This allows you to set a broad policy for a prefix and then refine or override it for specific sub-routes.

    export default defineNuxtConfig({
      // Global default
      security: {
        headers: {
          crossOriginEmbedderPolicy: 'require-corp'
        }
      }
      // Per route rules
      routeRules: {
        '/some-prefix/**': {
          security: {
            headers: {
              crossOriginEmbedderPolicy: false // Disables COEP for all /some-prefix/ routes
            }
          }
        },
        '/some-prefix/some-route': {
          security: {
            headers: {
              crossOriginEmbedderPolicy: 'credentialless' // Overrides prefix rule for this specific route
            }
          }
        }
      }
    })
  7. Limitations of origin-keyed agent clusters

    main

    When using Origin-Agent-Cluster: ?1, your page is placed in an origin-keyed agent cluster. This results in the following restrictions regarding communication with same-site cross-origin pages:

    • document.domain: You can no longer set document.domain. This prevents legacy synchronous DOM access between same-site cross-origin pages.
    • WebAssembly: You can no longer send WebAssembly.Module objects to other same-site cross-origin pages via postMessage().
    • Shared Resources (Chrome-only): You can no longer send SharedArrayBuffer or WebAssembly.Memory objects to other same-site cross-origin pages.
  8. Prevent Cross-Request State Pollution in Nuxt SSR

    main

    When using Nuxt in SSR mode, avoid using global ref or reactive variables in composables, as they can be shared unintentionally between different user requests, leading to data leaks. Instead, use the useState utility. useState ensures that state is isolated per request and is the recommended way to manage reactive state in Nuxt applications.

    // Avoid this (unsafe for SSR):
    const unsafeGlobal = ref<number>(1);
    
    // Use this (safe for SSR):
    const safeGlobal = useState<number>('safeGlobal', () => 5);
    
    export function useSafeRef() {
        const safeRef = useState<number>('safeRef', () => 2);
        return {
            safeGlobal,
            safeRef
        }
    }
  9. Use Nonces and Hashes for CSP Level 2

    main

    CSP Level 2 allows you to specify exactly which inline elements are permitted using nonces or hashes.

    Nonces

    A unique random number generated by the server for each request. The server sends the nonce in the HTTP header (e.g., script-src 'nonce-somerandomstring') and the same value must be added to the HTML element: <script nonce="somerandomstring">...</script>.

    Hashes

    A SHA hash of the inline code. The server sends the hash in the header (e.g., script-src 'sha256-...'), and the browser validates the inline script by hashing its content and comparing it to the header.

    Critical Nuxt Implications

    1. Cancellation: Using hashes or nonces cancels 'unsafe-inline'. Every single inline element must have a nonce or hash, or it will be blocked.
    2. Scope: Nonces and hashes only work for script-src and style-src. They cannot be used for other tags like <img> or <object>.
    3. Hydration Issues: CSP Level 2 is often ineffective for Nuxt because client-side hydration attempts to insert elements that lack the required nonce or hash, causing them to be blocked.
    4. SSG vs SSR: In SSR, nonces can whitelist both inline and external elements. However, for SSG (Static Site Generation), external elements must still be whitelisted by name (domain/file name).
    export defaultNuxtConfig({
      security: {
        headers: {
          contentSecurityPolicy: {
            "script-src": [
              "'nonce-{{nonce}}'",
              // nonce will allow inline scripts that are inserted server-side
              // But the application will block if client-side hydration tries to insert a script 
              "https:example.com" 
              // example.com must still be whitelisted by name to support SSG
              // example.com must still be whitelisted by name to support hydration
            ]
          }
        }
      }
    })
  10. Understand the challenges of implementing Strict CSP in Nuxt

    main

    Implementing a Strict Content Security Policy (CSP) in a Nuxt application requires addressing three core challenges:

    1. Resource Control: You must identify and authorize all scripts and stylesheets that Nuxt or its dependencies attempt to load. This includes distinguishing between external resources (loaded from a URL) and inlined resources (embedded directly in HTML).
    2. Hydration: Because Nuxt uses an isomorphic hydration mechanism, the browser may inject scripts and styles during the transition from server-rendered HTML to a client-side application. Your CSP must be designed to allow these hydration-related injections without being blocked.
    3. Rendering Mode (SSR vs. SSG):
      • In SSR (Server-Side Rendering) mode, Nuxt Security can leverage the Nitro server to control CSP delivery, typically using nonces.
      • In SSG (Static Site Generation) mode, files are served by a static provider rather than Nitro, so Nuxt Security uses hashes to authorize inline elements.
  11. Implement Strict CSP in SSG mode using hashes

    main

    For Static Site Generation (SSG) applications, nonces are not available. Instead, Nuxt Security implements Strict CSP via hashes injected into a <meta http-equiv="Content-Security-Policy"> tag.

    To enable this:

    1. Set security.ssg.meta to true to enable the meta tag.
    2. Set security.ssg.hashScripts to true to compute SHA hashes for inline and external scripts.
    3. Set security.ssg.hashStyles (defaults to false). Warning: Setting this to true may block Nuxt's client-side style hydration.
    4. Set security.sri to true to automatically calculate integrity attributes for bundled assets.
    export default defineNuxtConfig({
      security: {
        ssg: {
          meta: true, // Enables CSP as a meta tag in SSG mode
          hashScripts: true, // Enables CSP hash support for scripts in SSG mode
          hashStyles: false, // Recommended to keep false to avoid hydration issues
          exportToPresets: true
        },
        sri: true,
        headers: {
          contentSecurityPolicy: {
            'script-src': ["'strict-dynamic'"]
          }
        }
      }
    })
  12. Modify security options in runtime hooks

    main

    When using the nuxt-security:routeRules hook, you can choose how to apply your changes to existing rules:

    1. Merging with replacement: Use defuReplaceArray (auto-imported) to replace specific array values (like CSP directives) while preserving other security options for that route.
    2. Merging with addition: Use defu (requires import) to append new values to existing arrays (like adding a domain to script-src).
    3. Overwriting: Assign a new object directly to the route rule to erase all existing security settings for that route and replace them entirely.
    // 1. Merging with replacement (replaces specific array elements)
    import { defuReplaceArray } from 'defu'
    routeRules['/some/route'] = defuReplaceArray(
      { headers: { contentSecurityPolicy: { "script-src": ["'self'", "new-src"] } } },
      routeRules['/some/route']
    )
    
    // 2. Merging with addition (appends to existing arrays)
    import { defu } from 'defu'
    routeRules['/some/route'] = defu(
      { headers: { contentSecurityPolicy: { "script-src": ["'self'", "extra-src"] } } },
      routeRules['/some/route']
    )
    
    // 3. Overwriting (erases everything else)
    routeRules['/some/route'] = {
      headers: { contentSecurityPolicy: { "script-src": ["'self'"] } }
    }