Skip to content

nonce tag in __vitePreload (CSP) #9719

Closed

Description

@vinicius73

Description

I am trying to migrate or default tooling from Webpack to Vite.

The main issue is nonce security police.

Actually, we handle it using __webpack_nonce__

https://webpack.js.org/guides/csp/

I can't interact with the preload function to change it.

function preload(
baseModule: () => Promise<{}>,
deps?: string[],
importerUrl?: string
) {
// @ts-ignore
if (!__VITE_IS_MODERN__ || !deps || deps.length === 0) {
return baseModule()
}
return Promise.all(
deps.map((dep) => {
// @ts-ignore
dep = assetsURL(dep, importerUrl)
// @ts-ignore
if (dep in seen) return
// @ts-ignore
seen[dep] = true
const isCss = dep.endsWith('.css')
const cssSelector = isCss ? '[rel="stylesheet"]' : ''
// @ts-ignore check if the file is already preloaded by SSR markup
if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) {
return
}
// @ts-ignore
const link = document.createElement('link')
// @ts-ignore
link.rel = isCss ? 'stylesheet' : scriptRel
if (!isCss) {
link.as = 'script'
link.crossOrigin = ''
}
link.href = dep
// @ts-ignore
document.head.appendChild(link)
if (isCss) {
return new Promise((res, rej) => {
link.addEventListener('load', res)
link.addEventListener('error', () =>
rej(new Error(`Unable to preload CSS for ${dep}`))
)
})
}
})
).then(() => baseModule())
}

I'm already injecting the preload tags manually, JavaScript and CSS files.

Suggested solution

A way to inject nonce value simular to webpack.

Alternative

No response

Additional context

Example Application

https://github.com/nextcloud/text/blob/48b21b6e524c4aef78a1046e1f19a2bbd85837c4/src/main.js#L3-L4

Nextcloud apps

Nextcloud is modular, and allow the users to install a variety of apps.
These apps can inject scripts in many parts of nextcloud.

For secure reasons, we use CSP to prevent malicious script injections.

Content-Security-Policy: default-src 'none';base-uri 'none';manifest-src 'self';script-src 'nonce-cHQ...bz0=';style-src 'self' 'unsafe-inline';img-src 'self' data: blob:;font-src 'self' data:;connect-src 'self';media-src 'self';frame-src 'self';frame-ancestors 'self';form-action 'self'

Validations

Activity

  1. vinicius73 commented on Aug 23, 2022

    @vinicius73
    Author

    discussion related: #5218

  2. dagda1 commented on Sep 26, 2022

    @dagda1

    I have this problem also

  3. changed the title [-]nonce tag in __vitePreload [/-] [+]nonce tag in __vitePreload (CSP)[/+] on Sep 26, 2022
  4. VincentvD commented on Sep 28, 2022

    @VincentvD

    We're also facing this issue, coming from CRA/webpack. It would be awesome to have CSP nonce support in vite.

  5. kmcc049 commented on Oct 9, 2022

    @kmcc049

    We are also facing this problem. ATM it makes verifying a CSP is correct very hard. We are forced to set unsafe-inline during development.

  6. added
    p3-minor-bugAn edge case that only affects very specific usage (priority)
    on Oct 10, 2022
  7. seth-seikosoft commented on Nov 16, 2022

    @seth-seikosoft

    I'm just now running into this as well. I converted our apps to Vite a few months ago and I love using it, it's SO FAST.

    I've done a little googling trying to figure out if there is a plugin that will do this and I haven't found anything.

    Do we have any updates on this issue?

  8. Carminepo2 commented on Nov 19, 2022

    @Carminepo2

    Running into this issue too. I'm disabling all the dynamic import because I could not set the nonce token on the generated tags. Waiting for a solution

  9. krossekrabbe commented on Dec 27, 2022

    @krossekrabbe

    Please address this, it is impossible to use a sane CSP without it.

  10. tonymcneil commented on Feb 21, 2023

    @tonymcneil

    We ended up doing this:

    in vite.config.ts

    plugins: [
                {
                    name: "html-inject-nonce-into-script-tag",
                    enforce: "post",
                    transformIndexHtml(html: string) {
                        const regex = /<script(.*?)/gi;
                        const replacement = '<script nonce="{SERVER-GENERATED-NONCE}"$1';
                        return html.replace(regex, replacement);
                    },
                },
            ],
    

    All bundled script tags now get the placeholder {SERVER-GENERATED-NONCE} which is replaced with a random value by the server on each request.

  11. ottopaulsen commented on Mar 20, 2023

    @ottopaulsen

    @tonymcneil Thanks for this, I also got it working for script tags. However, i tried the same trick for style tags, but it does not work. Do you know how to get this working also for style tags?

  12. patrickglueck commented on May 5, 2023

    @patrickglueck

    @tonymcneil Thanks for this, I also got it working for script tags. However, i tried the same trick for style tags, but it does not work. Do you know how to get this working also for style tags?

    @ottopaulsen You can just go about it the same as for the script tags. Generic extendable example:

    plugins: [{
        name: "html-inject-data-preload-attr",
        enforce: "post",
        transformIndexHtml(html) {
            const regex = /<(link|style|script)/gi;
            const replacement = '<$1 data-preload="true"';
    
            return html.replace(regex, replacement);
        },
    }]
    
  13. ulken commented on May 16, 2023

    @ulken

    const replacement = '<$1 data-preload="true"';

    @patrickglueck Building custom AD B2C layouts are we? 😅

  14. patrickglueck commented on May 16, 2023

    @patrickglueck

    @ulken That is absolutely correct :D

  15. gregtwallace commented on Oct 21, 2023

    @gregtwallace
    Contributor

    If you want an extendable example that only adds nonces on the appropriate tags, I modified the above examples to:

      plugins: [
        {
          name: 'html-inject-data',
          enforce: 'post',
          transformIndexHtml(html) {
            const regex = /<(style|script|link) (.*)>/gi;
            const replacer = (_, p1, p2) => {
              // add nonce?
              if (
                p1 === 'style' ||
                p1 === 'script' ||
                // if link, only nonce for stylesheet
                (p1 === 'link' && p2.includes('rel="stylesheet"'))
              ) {
                p2 = `nonce="{SERVER-CSP-NONCE}" ${p2}`;
              }
    
              return `<${p1} data-preload="true" ${p2}>`;
            };
    
            return html.replace(regex, replacer);
          },
        },
      ],
    

    Preload tag is added to all style, script, and link tags but nonce is only added to style, script, and rel="stylesheet" links.

  16. q7314568 commented on Oct 26, 2023

    @q7314568

    If you want an extendable example that only adds nonces on the appropriate tags, I modified the above examples to:

      plugins: [
        {
          name: 'html-inject-data',
          enforce: 'post',
          transformIndexHtml(html) {
            const regex = /<(style|script|link) (.*)>/gi;
            const replacer = (_, p1, p2) => {
              // add nonce?
              if (
                p1 === 'style' ||
                p1 === 'script' ||
                // if link, only nonce for stylesheet
                (p1 === 'link' && p2.includes('rel="stylesheet"'))
              ) {
                p2 = `nonce="{SERVER-CSP-NONCE}" ${p2}`;
              }
    
              return `<${p1} data-preload="true" ${p2}>`;
            };
    
            return html.replace(regex, replacer);
          },
        },
      ],
    

    Preload tag is added to all style, script, and link tags but nonce is only added to style, script, and rel="stylesheet" links.

    does this work for vite react?i cant make it work for static file.

  17. gregtwallace commented on Oct 26, 2023

    @gregtwallace
    Contributor

    I am using it with Vite and React, yes:

    https://github.com/gregtwallace/legocerthub-frontend/blob/master/vite.config.js

    The snipet I posted doesn’t include any other plugins like react().

  18. q7314568 commented on Oct 27, 2023

    @q7314568

    I am using it with Vite and React, yes:

    https://github.com/gregtwallace/legocerthub-frontend/blob/master/vite.config.js

    The snipet I posted doesn’t include any other plugins like react().

    Thanks for reply!

    I try to run your repo,but didnt find where is the csp tag,

    I put csp like the following:

    <meta name="csp" http-equiv="Content-Security-Policy" content="default-src 'self'; font-src 'self' data:; script-src 'self'; style-src 'self' 'nonce-value'; img-src 'self' data:; connect-src 'self' %VITE_API_URL%;" data-rh="true" />

    and even with the setting in vite.config,the emotion and react/fontawsome still throw the error.

  19. gregtwallace commented on Oct 29, 2023

    @gregtwallace
    Contributor

    The script only injects the nonce into what’s in the html. Emotion injects styles using js which is not yet supported in Vite. For now I’m using unsafe-inline for styles.

  20. q7314568 commented on Oct 29, 2023

    @q7314568

    The script only injects the nonce into what’s in the html. Emotion injects styles using js which is not yet supported in Vite. For now I’m using unsafe-inline for styles.

    Finally i found a way to inject nonce value for emotion.js,thanks for your reply anyway.

  21. tvongaza commented on Oct 29, 2023

    @tvongaza

    The script only injects the nonce into what’s in the html. Emotion injects styles using js which is not yet supported in Vite. For now I’m using unsafe-inline for styles.

    Finally i found a way to inject nonce value for emotion.js,thanks for your reply anyway.

    Mind sharing what you did @q7314568 ? Facing a similar problem with a different library and curious how you solved it.

  22. gregtwallace commented on Oct 30, 2023

    @gregtwallace
    Contributor

    I'm interested in your workaround too.

    The only remotely sane way I've found is to add the emotion cache (https://emotion.sh/docs/@emotion/cache) with a placeholder string for nonce and then inline everything using https://github.com/richardtallent/vite-plugin-singlefile. Then the backend can replace the placeholder string in the inlined js inside index.html.

    This is bad for a number of reasons and I've chosen to leave my app as unsafe-inline until Vite provides proper support to precompile the css and do a stylesheet import.

  23. tvongaza commented on Oct 30, 2023

    @tvongaza

    The fix in #14653 seems closer to what I would expect to work. I've tried out a similar fix pulling the CSP nonce from a meta tag and it does work in my use case. The fix here is relatively small/simple, be nice if Vite would bring it in already.

  24. q7314568 commented on Oct 31, 2023

    @q7314568

    The script only injects the nonce into what’s in the html. Emotion injects styles using js which is not yet supported in Vite. For now I’m using unsafe-inline for styles.

    Finally i found a way to inject nonce value for emotion.js,thanks for your reply anyway.

    Mind sharing what you did @q7314568 ? Facing a similar problem with a different library and curious how you solved it.

    I mainly face the issue with emotion.js and react/fontawesome,the emotion.js can deal with the solution @gregtwallace offer,use cahce to inject nonce value to inline script/style.

    About react/fontawesome,check https://fontawesome.com/docs/web/dig-deeper/security

    use

    import { config } from '@fortawesome/fontawesome-svg-core';
    
    // Make sure this is before any other `fontawesome` API calls
    config.autoAddCss = false

    disable the auto add,then import the style from node_module instead,

    but the above solution only work for prod,so i change CSP rule by env variable,

    dev still using unsafe-inline,and prod mode change to fixed nonce value.

    i am also keeping my eyes on the feature @tvongaza metiond,still hope vite give us a way to cover both dev/prod for this.

  25. gregtwallace commented on Oct 31, 2023

    @gregtwallace
    Contributor

    Not sure what backend you all are using, but I ended up working around the issue last night.

    1. Added meta tag with the csp-nonce property for the server to inject the real nonce when serving the .html file.
    2. Manually chunked the offending js module (to minimize the size of the file that needs to be modified).
    3. When the backend serves the offensive file (small chunked module), it uses regex to modify a line of code that sets the nonce to the meta tag's nonce, instead of the usual nonce source (in the case of emotion, the emotion cache).

    While this required me doing some work to my backend server, it saved me from having to customize Vite. An alternative would be to manually edit the build after completion, but I opted for the on the fly modification to avoid the manual step.

    see: gregtwallace/certwarden-backend@f127749
    and: gregtwallace/certwarden-frontend@7ea6ec1

  26. JohannesSchwegler commented on Nov 13, 2023

    @JohannesSchwegler

    I think I found a workaround without passing the nonce to vite directly. By adding the property 'script-dynamic' (https://content-security-policy.com/strict-dynamic/) to the script-src properties, everythings seems to work fine and I could remove the 'unsafe-inline' property.

    Here is a short summary of my setup:

    1. Add a nonce plugin like the ones posted by other contributors in this thread.
    const noncePlugin = (placeholderName = 'nonce_by_nginx'): PluginOption => ({
      name: 'add-nonce-script-attr',
      enforce: 'post',
      transformIndexHtml(html) {
        return html
          .replace(new RegExp('<script', 'g'), `<script nonce="${placeholderName}"`)
          .replace(new RegExp('<link', 'g'), `<link nonce="${placeholderName}"`)
      }
    })

    2.Replace the "nonce_by_nginx"-placeholder with a nonce on the server. I used the nginx_substituion_module for this.

    subs_filter nonce_by_nginx $request_id;
    1. Add the nonce and script-dynamic to the script-src part of the csp header:
    script-src  'self' 'nonce-$request_id' 'strict-dynamic'
    

    Hope this helps :)

  27. locked and limited conversation to collaborators on Mar 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    p3-minor-bugAn edge case that only affects very specific usage (priority)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions