Skip to content

Documentation: Clarify limitations of html.cspNonce for SPA deployments. #20531

@sebastiancarlos

Description

@sebastiancarlos

Documentation is

  • Missing
  • Outdated
  • Confusing
  • Not sure?

Explain in Detail

Update:

Thanks to the clarification from @sapphi-red, I now have a better understanding of the intended mechanism. My initial report had some incorrect assumptions:

  1. I now understand the <meta property="csp-nonce"> is for Vite's internal use, not for setting the policy itself.
  2. I also understand that html.cspNonce should be set to a literal placeholder string (e.g., 'CSP_NONCE_PLACEHOLDER'), not a randomly generated value within vite.config.js. This is correctly documented.

My core concern remains, however, that the documentation doesn't adequately guide the user on how to implement the required server-side replacement, especially in common deployment scenarios like SPAs, where it might interfere with caching. I've detailed this in my follow-up comment below, suggesting that maybe the docs should point to hash-based CSP as a better solution for the typical Vite user.

Original:

Hey there,

First off, thank you for building and maintaining Vite!

As documented elsewhere, notably here and here, there might be some amount of perceived "security theater" on Vite's documentation regarding the Content Security Policy (CSP) info mentioned prominently on the "Features" page:

Content Security Policy (CSP)

To deploy CSP, certain directives or configs must be set due to Vite's internals.

'nonce-{RANDOM}'

When html.cspNonce is set, Vite adds a nonce attribute with the specified value to any <script> and <style> tags, as well as <link> tags for stylesheets and module preloading. Additionally, when this option is set, Vite will inject a meta tag (<meta property="csp-nonce" nonce="PLACEHOLDER" />).

The nonce value of a meta tag with property="csp-nonce" will be used by Vite whenever necessary during both dev and after build.

WARNING: Ensure that you replace the placeholder with a unique value for each request. This is important to prevent bypassing a resource's policy, which can otherwise be easily done.

First of, a bit of a CSP 101:

  • Basically, CSP is a set of security measures in which the server should send, for all resource URIs, a Content-Security-Policy header, containing some restrictions for the user argent to follow, regarding from which origins or URIs the user agent can load resources.
    • It helps mitigate attacks like Cross-Site Scripting (XSS)
    • Examples: Content-Security-Policy: default-src 'self'; img-src 'self' https://example.com;
    • The keyword default-src defines the basic policy, which can then be tuned for particular resource types, such as script-src.
    • The value self limits new resources to those with the same origin.
    • Typically, you would enable 'self', plus trusted origins such as CDNs.
  • Also, the nonce mechanism is used to limit execution of inline scripts to those for which a corresponding nonce attribute is present on the script tag.
    • As the name implies, each response should contain an unique nonce.
    • Example:
      <!-- Content-Security-Policy: script-src 'self' 'nonce-abc123'; -->
      <script nonce="abc123">console.log("Hello, world!");</script>
      <script>console.log("This will not run!");</script>
  • The CSP information could be provided on a <meta> tag, but this is considered less secure than on the HTTP Header. (source)

Back to html.cspNonce

Now, the issues I find when trying to implement this feature are the following:

  • The meta tag is not the right way to implement this. It should be done with a Content-Security-Policy HTTP header if possible (source)
  • If you generate a random nonce both for the header and for the html.cspNonce option, on the vite.config.js, Vite would read this file twice at different times to generate one or the other, entirely breaking your site by not having a matching nonce everywhere.
  • Even worse, if you build a SPA with vite build, the nonce will be generated only once, which is not secure at all, as it should be different for every request. Not to mention that nonce is not compatible with static content which is usually cached.
  • Some partial measures exist, such as the plugin vite-plugin-csp-guard, but it notoriously documents that it doesn't generate a nonce for SPAs (source), rather relying on "hash-based CSP."

This discovery process can be frustrating and time-consuming. More importantly, a developer might miss the nuance and deploy a statically-generated nonce, creating a false sense of security. The fact that this is a known point of confusion is validated by existing issues linked above.

Your Suggestion for Changes

I propose that the documentation for the html.cspNonce option be updated to explicitly state its limitations and current recomended usage (if any). Adding a prominent warning or tip would be incredibly effective.

Thank you again for your time and for all your hard work on Vite.

Reproduction

No response

Steps to reproduce

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions