Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nonce tag in __vitePreload (CSP) #9719

Closed
4 tasks done
vinicius73 opened this issue Aug 17, 2022 · 24 comments · Fixed by #16052
Closed
4 tasks done

nonce tag in __vitePreload (CSP) #9719

vinicius73 opened this issue Aug 17, 2022 · 24 comments · Fixed by #16052
Labels
p3-minor-bug An edge case that only affects very specific usage (priority)

Comments

@vinicius73
Copy link

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

@vinicius73
Copy link
Author

discussion related: #5218

@dagda1
Copy link

dagda1 commented Sep 26, 2022

I have this problem also

@vinicius73 vinicius73 changed the title nonce tag in __vitePreload nonce tag in __vitePreload (CSP) Sep 26, 2022
@VincentvD
Copy link

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

@kmcc049
Copy link

kmcc049 commented Oct 9, 2022

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.

@patak-dev patak-dev added the p3-minor-bug An edge case that only affects very specific usage (priority) label Oct 10, 2022
@seth-seikosoft
Copy link

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?

@Carminepo2
Copy link

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

@krossekrabbe
Copy link

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

@maccuaa maccuaa mentioned this issue Feb 6, 2023
9 tasks
@tonymcneil
Copy link

tonymcneil commented Feb 21, 2023

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.

@ottopaulsen
Copy link

@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?

@patrickglueck
Copy link

patrickglueck commented May 5, 2023

@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);
    },
}]

@ulken
Copy link

ulken commented May 16, 2023

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

@patrickglueck Building custom AD B2C layouts are we? 😅

@patrickglueck
Copy link

@ulken That is absolutely correct :D

@gregtwallace
Copy link
Contributor

gregtwallace commented Oct 21, 2023

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.

@q7314568
Copy link

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.

@gregtwallace
Copy link
Contributor

gregtwallace commented Oct 26, 2023

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().

@q7314568
Copy link

q7314568 commented Oct 27, 2023

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.

@gregtwallace
Copy link
Contributor

gregtwallace commented Oct 29, 2023

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.

@q7314568
Copy link

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.

@tvongaza
Copy link

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.

@gregtwallace
Copy link
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.

@tvongaza
Copy link

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.

@q7314568
Copy link

q7314568 commented Oct 31, 2023

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.

@gregtwallace
Copy link
Contributor

gregtwallace commented Oct 31, 2023

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

@JohannesSchwegler
Copy link

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 :)

@github-actions github-actions bot locked and limited conversation to collaborators Mar 28, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
p3-minor-bug An edge case that only affects very specific usage (priority)
Projects
None yet