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 implementation for style src #239

Closed
erikknorren opened this issue Oct 9, 2023 · 10 comments
Closed

Nonce implementation for style src #239

erikknorren opened this issue Oct 9, 2023 · 10 comments
Labels
question Further information is requested

Comments

@erikknorren
Copy link

Hello,

I recently tried implementing nonce based content-security-policy from the documentation using the config listed below. The script tags in my head all have the nonce attribute and work fine this way. However, my style tags do not. In my app.vue I did the following to try to get it to work as well, however my stylesheets cannot be loaded into the DOM. Is this a bug or am I doing something wrong?

onMounted(() => {
  // set nonce to all style tags
  const styleTags = document.querySelectorAll('style')
  for (const styleTag of styleTags) {
    styleTag.setAttribute('nonce', useNonce())
 }
})
security: {
    nonce: true,
    headers: {
      crossOriginEmbedderPolicy: 'unsafe-none',
      contentSecurityPolicy: {
        'img-src': ['*', 'data:', 'https:'],
        'style-src': [
          "'self'", // fallback value for older browsers, automatically removed if `strict-dynamic` is supported.
          "'nonce-{{nonce}}'",
        ],
        'script-src': [
          "'self'", // fallback value for older browsers, automatically removed if `strict-dynamic` is supported.
          "'nonce-{{nonce}}'",
          "'strict-dynamic'",
        ],
        'script-src-attr': [
          "'self'", // fallback value for older browsers, automatically removed if `strict-dynamic` is supported.
          "'nonce-{{nonce}}'",
          "'strict-dynamic'",
        ],
      },
    },
  },
@erikknorren erikknorren added the question Further information is requested label Oct 9, 2023
@Baroshem
Copy link
Owner

Baroshem commented Oct 9, 2023

Hey, thanks for rainsing a question.

I would love to get some feedback from @trijpstra-fourlights who is an expert of nonce ;)

@trijpstra-fourlights
Copy link
Contributor

I wouldn't call myself an expert per se ;), but I'm happy to try and help!

The nonce attribute cannot be read at runtime by the browser, nor set retroactively which is essentially what you're doing in the onMounted() snipplet.

This is by design (as in, by the browser) because allowing that would defeat the whole purpose of the nonce: "hello browser, this specific resource is absolutely trusted by me, the server. You can check this because the nonce attribute is set by me and it matches the CSP header."

To get nonce working, you'll have to provide the nonce attribute when the actual <style> element is created, or use the useHead composable which nuxt-security supports out of the box.

It makes sense if you think about it:

if the code in your OP was actually working, it would essentially result in the same as having a CSP with style-src: unsafe-inline http: https: as you are blindly trusting anything that has a style tag, i.e. "whatever style element is there, it's trusted".

So either use that as your CSP for style-src, or use useHead, or supply the nonce attribute when creating the <style> element (either in SFC/JSX or with document.createElement).

Hope that helps! If not, can you show how you're injecting/creating the <style> elements that you want to supply with the nonce value?

@erikknorren
Copy link
Author

erikknorren commented Oct 10, 2023

Thank you very much! I believe I understand what you mean by letting the client generate the nonce after the <style> tag has already been served to the DOM. I use nuxt.config to add either Tailwind or Vuetify to my style, so useHead() isn't really an option I think. My issue ended up being only relevant locally. When importing my stylesheets in app.vue <style> rather than in config it works on production. Thanks a lot!

@trijpstra-fourlights
Copy link
Contributor

Thank you very much! I believe I understand what you mean by letting the client generate the nonce after the <style> tag has already been served to the DOM. I use nuxt.config to add either Tailwind or Vuetify to my style, so useHead() isn't really an option I think. My issue ended up being only relevant locally. When importing my stylesheets in app.vue <style> rather than in config it works on production. So I ended up doing this:

[...]

Thanks a lot!

No problem, glad that you got it working 👍

It does ring some bells, so I checked my reference project and I indeed also have a similar check implemented:

security: {
    nonce: true,
    headers: {
      contentSecurityPolicy: {
        'style-src':
          process.env.NODE_ENV === 'production'
            ? [
                "'self'", // backwards compatibility for older browsers that don't support strict-dynamic
                "'nonce-{{nonce}}'",
                "'strict-dynamic'",
              ]
            : // In dev mode, we allow unsafe-inline for hot reloading
              ["'self'", 'https:', "'unsafe-inline'"],
        'script-src': [
          "'self'", // backwards compatibility for older browsers that don't support strict-dynamic
          "'nonce-{{nonce}}'",
          "'strict-dynamic'",
        ],
        'script-src-attr': ["'self'", "'nonce-{{nonce}}'"],
      },
    },
  },

@Baroshem we should probably add this to the nonce documentation, what do you think?

@Baroshem
Copy link
Owner

That would be really useful! @trijpstra-fourlights could you create a PR to branch 1.0.0-rc.1? :)

@erikknorren
Copy link
Author

Thanks, this works out great. I have one more question, if my component framework (like Vuetify) uses inline-styles i'd have to use unsafe inline right?

@trijpstra-fourlights
Copy link
Contributor

trijpstra-fourlights commented Oct 10, 2023

Thanks, this works out great. I have one more question, if my component framework (like Vuetify) uses inline-styles i'd have to use unsafe inline right?

If you cannot control how these inline styles get injected, then indeed your only option to use unsafe-inline.

While I haven't tested this, it seems that vuetify supports supplying a nonce (although they use a static one in their example, which defeats the purpose).

Also found this issue which provides some insight on how to approach this:

We need to add the cspNonce entry in the theme, you can try generating the cspNonce, add a Nuxt Plugin, register vuetify:before-create hook and apply the cspNonce to the theme.

So if you hook into the vuetify:before-create hook, you could try to get the current nonce value const nonce = useNonce() and apply that to the cspNonce parameter on theme.

I haven't tested that though, so YMMV. Let us know if you are able to figure it out!
If not, I can probably help you out on getting it to work, but I can't promise on when I'll have time to take a proper stab at it..., so if it's blocking you, your best bet is to go for unsafe-inline for now.

edit (1):

tbh, I don't think before-create will be the proper hook for this. The nonce value will need to be read (and injected) at runtime (on every request), so suspect you'll need a runtime hook, something like vuetify:before-render but that doesn't seem to exist. Perhaps you can provide a function to the cspNonce parameter, then you could try something like

theme: {
  cspNonce: () => useNonce()
}

again... this is untested and I suspect it won't work.

edit (2):

Another thing I just remembered, nuxt-security will (using a nitro plugin) try to add the current nonce value to all script and style tags that are present in the outputted HTML. So if vuetify is injected before that nitro-hook is called, then I'd assume they all get a proper nonce. You should be able to see that locally as well (i.e. on non-production) by inspecting the actual HTML returned by Nuxt.

@erikknorren
Copy link
Author

erikknorren commented Oct 10, 2023

Thanks, you are a saint. I tried to incorporate this but alongside <style> tags, vuetify also inserts inline styles in the html. Such as:
image
The style tags in the all get proper nonces even without the the theme approach you illustrated. I am quite new to this but I am assuming a nonce cannot be added to an html element that has an inline style attribute rather than a <style> tag element?

Edit:
I can safely use Vuetify with the documented security configuration, all of the vuetify elements look exactly how they are supposed to on production. The only problem is the amount of errors:
image

If my assumption is correct is either accept these errors or use unsafe-inline when using Vuetify. This does not seem to be a nuxt-security issue.

@trijpstra-fourlights
Copy link
Contributor

trijpstra-fourlights commented Oct 10, 2023

Thanks, you are a saint. I tried to incorporate this but alongside <style> tags, vuetify also inserts inline styles in the html. Such as: image The style tags in the all get proper nonces even without the the theme approach you illustrated. I am quite new to this but I am assuming a nonce cannot be added to an html element that has an inline style attribute rather than a <style> tag element?

Edit: I can safely use Vuetify with the documented security configuration, all of the vuetify elements look exactly how they are supposed to on production. The only problem is the amount of errors: image

If my assumption is correct is either accept these errors or use unsafe-inline when using Vuetify. This does not seem to be a nuxt-security issue.

The style attribute on normal DOM elements is controlled by the style-src-attr in CSP. The nonce value can be used there as well, but unfortunately the current implementation in nuxt-security does not scan on these DOM inline-styles.

I do think this is something that should be added to nuxt-security, but it's not a trivial change as it will potentially add overhead on every request (as nitro is essentially scanning the whole HTML and inserting nonce values). That's a decision to be made by @Baroshem et al.

We can make the feature dependent on the actual implemented CSP headers, but again, such a change will need to be properly tested and documented.

That being said, I wouldn't go to production with CSP errors, and I would always then choose to implement a less strict CSP, so in your case use unsafe-inline for the style-src-attr.

IMO, Vuetify should allow for a dynamic, runtime-available value of nonce which then gets injected in their inline styles, but I can imagine that's probably not a trivial change for them due to build-time optimizations etc.

I think the take home message is that nuxt-security should handle style-src-attr, but also that implementing a fully strict CSP is not always possible, especially with external dependencies.

I will try to get a playground project going which uses tailwind, vuetify & nuxt-security with a fully strict CSP, but I can't make any promises on when I'll have the time for that. I will link it in this thread when it's available though.

edit (1): wording, formatting

@Baroshem
Copy link
Owner

Baroshem commented Oct 11, 2023

Thanks for extensive research @trijpstra-fourlights and for additional details @erikknorren 💚

After reading your conversion, in my honest opinion, I dont think that this should be added to Nuxt Security.

In general, it would be useful, but it is a bit of an edge case right now that requires a functionality that as you mentioned will create some overhead for all module users (not only those who are using it) which can be troublesome for performance for example.

This case of Vuetify inline styles shouldnt be a reason to add a feature that will add some dead code for other users. And considering that this issue can be easily mitigated with setting less strict CSP, I would recommend to follow this path now.

But lets keep this idea in mind. Maybe in the future versions, we will be able to implement similar functionality without causing overhead.

Thanks once again for everything!

@trijpstra-fourlights could you add these docs about nonce from your previous comment? :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants