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

Modify interface for CSP options #277

Closed
vejja opened this issue Nov 2, 2023 · 8 comments
Closed

Modify interface for CSP options #277

vejja opened this issue Nov 2, 2023 · 8 comments
Labels
enhancement New feature or request

Comments

@vejja
Copy link
Collaborator

vejja commented Nov 2, 2023

Is your feature request related to a problem? Please describe.

Currently, the options to set up CSP headers in Nuxt Security are found in different places

  • in the headers: contentSecurityPolicy header, with optionally a 'nonce-{{nonce}}' placeholder
  • in the nonce: boolean option for SSR
  • in the ssg: hashScripts option for SSG

Our users can sometimes get confused on how to setup CSP properly, depending on whether they are trying to use CSP-strict mode and whether they are using SSR or SSG modes.

Describe the solution you'd like

1- eliminate the 'nonce-{{nonce}}' placeholder from headers
2- regroup CSP setup under a new option:

{ // Better naming possible...
  cspNonceOrHashes: {
    addToScriptSrc: boolean; // default true
    addToStyleSrc: boolean; // default false
  }
}

Rationale for the Proposal

1- On the suppression of the 'nonce-{{nonce}}' placeholder: this parameter can conflict with the current nonce option. If 'nonce-{{nonce}}' is used but nonce is set to false, it blocks the page. The opposite is also true. In addition, nonce: true is ignored with SSG. Finally, our users sometimes use 'nonce-{{nonce}}' on non-nonceable elements, leading to questions as to why the page blocks.
2- On the introduction of the cspNonceOrHashes option: this parameter makes it clear that we are dealing with both SSR and SSG at the same time. There is no more need to think about the differences between SSR (nonce option) and SSG (ssg option), Nuxt Security will use nonces for SSR and hashes for SSG automatically. In addition, it makes it clear where the nonce or hash will be included, and what sensible defaults should be used.

Implementation Guidelines

The implementation SHOULD follow the following logic:

  • if cspNonceorHashes.addToScriptSrc is true:

    • if rendering mode is SSR:
      • parse all inline scripts, add nonce attribute to HTML
      • parse all external scripts (including in links), add nonce attribute to HTML
      • add nonce to script-src policy
    • if rendering mode is SSG:
      • parse all inline scripts, hash content, add SHA to script-src policy
      • parse all external scripts (including in links), find integrity attribute, add SHA to script-src policy.
      • If 'strict-dynamic' is used and at least one external script does not have an integrity attribute, send a warning to the user that this script will be blocked because of 'strict-dynamic' (consider alternative: disable 'strict-dynamic' and warn the user that we are removing the keyword)
  • else:

    • return
  • if cspNonceorHashes.addToStyleSrc is true:

    • if rendering mode is SSR:
      • parse all inline styles, add nonce attribute to HTML
      • parse all external styles (including in links), add nonce attribute to HTML
      • add nonce to style-src policy
    • if rendering mode is SSG:
      • parse all inline styles, hash content, add SHA to style-src policy
      • parse all external styles (including in links), find integrity attribute, add SHA to style-src policy
  • else:

    • return

The old ModuleOptions syntax MAY be marked as deprecated but SHOULD continue to be supported in future versions.

Describe alternatives you've considered

A first alternative would be to use a more generic term such as {{nonceOrHash}} placeholders in headers: contentSecurityPolicy. This would make it clear that the nonce or hash will be included in the corresponding section of the CSP header. It's a clean alternative, that eliminates the need for both the nonce option and the ssg option. However it does not prevent users from including this placeholder on non-nonceable elements, nor does it make clear that we can only process <script> and <style> elements, and that scripts should be processed by default but styles should not be processed by default.

Another alternative would be to fully deprecate the headers: contentSecurityPolicy option, and regroup all CSP functionalities under a comprehensive csp option. This would make sense as CSP is a separate and complex feature in its own, and setting its options via the headers syntax is not easy. In addition CSP is not transmitted via headers but via <meta> tag when using SSG. However CSP is also a HTTP header, and it also makes sense to indicate that we will send the header in SSR mode.

Additional context

The background for this proposal lies fundamentally in the CSP syntax itself, which is well-known to be incredibly complex.

  1. In CSP Level 1, when 'unsafe-inline' is used for script-src or style-src, inline scripts and styles are allowed. However this is not recommended
  2. In CSP Level 2, when a nonce or hash is added for script-src or style-src, this cancels the 'unsafe-inline' directive. In other words, any inline script or style not corresponding to the nonce or hash will be disallowed. However external scripts and styles can still be whitelisted individually.
  • This has a consequence for Nuxt: we can detect all inline scripts and styles at server-side render time. But on the client-side, if a script injects another inline script or inline style, it will block the site.
  • As a result, we need to provide the option for the user to disable the insertion of the nonce or hash in both the script-srcand the style-src policies
  1. In CSP Level 3, part of this issue was solved with 'strict-dynamic'. If used, this keyword enables any parent script authorized by nonce or hash to insert any other child script. But this came with an additional restriction, which is that external scripts cannot be whitelisted anymore.
  • This has a new consequence for Nuxt: we now need to detect external scripts to add their hash or nonce. The client-side problem is now solved, because any parent can now insert any child. On the server-side though, the situation now becomes a bit more complex: in SSR this is fine because we can insert nonces, but in SSG we need to find the hash in an integrity attribute. Unfortunately this is how it works, so in the SSG case there are two possibilities:
    • either the user is able to insert integrity attributes to all of its external scripts, in which case we are ok
    • if not (e.g. because the hosting service for the external script does not provide integrity hashes), it is not possible to use 'strict-dynamic' and we are back to CSP Level 2.
    • As a result, we should advise the user to disable 'strict-dynamic' only in the SSG case
  • There is another indirect consequence for Nuxt: scripts can inject scripts, but scripts cannot inject styles. Unfortunately this is how it works, 'strict-dynamic' only applies to scripts and not to styles. So, as far as styles are concerned, we are always back to CSP Level 2.
    • As a result, we still need to provide the option for the user to disable the insertion of the nonce or hash only in the style-src policy
@vejja vejja added the enhancement New feature or request label Nov 2, 2023
@Baroshem
Copy link
Owner

Baroshem commented Nov 3, 2023

Hey @vejja

Thanks for this extensive research and description! I really like the idea of making things simpler.

I am totally into getting rid of nonce in the headers if it is possible. This would make working with CSP and nonce much smoother.

However, for your proposed change about merging nonce and ssg options, I think this is not a good idea.

The module is aimed for SSR apps mainly (due to the usage of server middleware). What the user gets while using this module in SSG is the CSP delivered as http-equiv, remove console loggers, and some smaller utils. That is why, I think that additional configuration option for ssg is fine.

For the nonce however, it would be useful to have is as a part of security.headers.contentSecurityPolicy but the current interface (that is also generic for other headers) is that inside security.headers.contentSecurityPolicy you would just get the HTTP directives. And nonce is a special case of CSP that you may want to use but you dont have to. It is an additional layer of protection.

So in summary,

  • I think that getting rid of nonce placeholder in headers is a good idea (if we can deliver it to the users without requiring them to write additional code or refactor existing solution because of breaking changes)
  • I am against merging nonce and ssg. I think they are in the right places :)

Let me know what you think about it :)

@vejja
Copy link
Collaborator Author

vejja commented Nov 3, 2023

Ok, it makes sense and agree it will work for our users.

The thing I'm trying to scratch my head around is the following

  • In SSR mode, setting script-src 'self' 'unsafe-inline' 'strict-dynamic' and style-src 'self' https: 'unsafe-inline' should be the reasonable default. It will work in all cases and ensure a strict CSP, if nonces are not added to the style-src policy.
  • In SSG mode, this will work also in the majority of cases, but it will break sites that include an external script (think Google Analytics, Bootstrap, etc.), if an integrity hash is not included on that external resource.

On the first point, this means that we need to keep placeholders.
On the second point, unfortunately we are constrained by how CSP works. It should then be the responsibility of the user to include integrity tags manually to comply with CSP if they want to enable CSP.

Maybe I can try to clarify this in the docs, WDYT?

@Baroshem
Copy link
Owner

Baroshem commented Nov 3, 2023

I think the docs should do the trick. We don't have to deliver everything to the user. It would be awesome but as you know, security is not easy ;)

So giving users right tools is also a fine approach IMO. Let's give them tools and instructions on how they can solve their issues :)

@vejja
Copy link
Collaborator Author

vejja commented Nov 3, 2023

Ok then I'll update the docs
Before that, can you confirm I understand correctly

  • We will keep the nonce-{{nonce}} placeholders. This is necessary to make sure that in SSR mode, the user can elect to disable nonces in either style-src or script-src, while keeping nonces in the other one. In SSG mode, these placeholders will be ignored.
  • We will keep the nonce option. Setting to true means that the nonce will be added to all HTML elements (styles and scripts, both inline and external).
  • We will keep the ssg option, with sub-properties hashScripts and hashStyles. This is necessary to make sure that in SSG mode, the user can elect to disable hashes in either style-src or script-src, while keeping hashes in the other one. In SSR mode, this option will be ignored.
  • In addition, in SSG mode, all external scripts and styles will be looked up for integrity attributes, and the corresponding hashes will be inserted in script-src (if hashScripts is true) and style-src (if hashStyle is true)

In summary:
In SSR mode, nonce insertion in the CSP policy is governed via placeholders, and nonce insertion in HTML is governed by the nonce option
In SSG mode, hashes insertion in the CSP policy is governed via ssg option

@Baroshem
Copy link
Owner

Baroshem commented Nov 6, 2023

Yes, I think this approach should be safe. It could be better of course but this will lead to breaking changes that I wont to avoid now :)

Thanks for doing the summary!

Maybe we could add a note in the documentation about usage of the module in SSR vs SSG? With the summary of what user get by using certain approach

What do you think about it?

@vejja
Copy link
Collaborator Author

vejja commented Nov 6, 2023

I am clarifying the docs section right now + adding a brand new section in 'Advanced' on CSP to cover edge cases
I will submit via PR but I think you will want to do some editing to ensure it looks good to you

@Baroshem
Copy link
Owner

Baroshem commented Nov 6, 2023

Awesome, let me know once it is ready. I will do my best to help.

Thanks for everything. I really appreciate it!

@vejja
Copy link
Collaborator Author

vejja commented Nov 6, 2023

🚀 PR #282
Will close this issue now

@vejja vejja closed this as completed Nov 6, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants