Baroshem / nuxt-security

🛡 Automatically configure your app to follow OWASP security patterns and principles by using HTTP Headers and Middleware
https://nuxt-security.vercel.app/
MIT License
824 stars 58 forks source link

CSP fails with strict-dynamic when implementing Cloudflare Web Analytics #546

Closed adamdehaven closed 2 weeks ago

adamdehaven commented 2 weeks ago

When following the @nuxt/scripts implementation instructions for Cloudflare Web Analytics here: https://scripts.nuxt.com/scripts/analytics/cloudflare-web-analytics (the "Loading Globally" section, as well as "useScriptCloudflareWebAnalytics" my CSP settings are consistently producing the following error:

Refused to load the script 'https://static.cloudflareinsights.com/beacon.min.js' because it
violates the following Content Security Policy directive: 
"script-src 'self' https: 'unsafe-inline' 'strict-dynamic' 'unsafe-eval' ...[a bunch of sha]".
Note that 'strict-dynamic' is present, so host-based allowlisting is disabled. 
Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.

Here's my nuxt.config.ts entry for the module:

security: {
  headers: {
    crossOriginEmbedderPolicy: 'unsafe-none',
    crossOriginOpenerPolicy: false, // Resolves a NS_ERROR_FAILURE error in Firefox
    xFrameOptions: 'SAMEORIGIN',
    contentSecurityPolicy: {
      'upgrade-insecure-requests': import.meta.env.NODE_ENV === 'development' ? false : true, 
      'img-src': ["'self'", 'data:', 'https:'],
      'frame-ancestors': ["'self'", 'localhost:*', 'http://localhost:*', 'https://localhost:*', 'https://*.example.com'],
      'script-src': [
        "'self'",
        'https:',
        "'unsafe-inline'",
        "'strict-dynamic'",
        "'nonce-{{nonce}}'",
        "'unsafe-eval'",
      ],
      'script-src-attr': ["'self'"],
      'style-src': [
        "'self'", 
        'https:',
        "'unsafe-inline'",
      ],
    },
  },
  rateLimiter: false,
  removeLoggers: false,
}

According to the Nuxt security docs on whitelisting external resources utilizing useScript (or the global loader, I'm assuming) should bypass this issue because of the strict-dynamic rule.

If I remove strict-dynamic from my config, the issue is resolved, but this isn't ideal.

What am I doing wrong? Any help would be appreciated.

vejja commented 2 weeks ago

Hi @adamdehaven Can you tell me

Thanks

adamdehaven commented 2 weeks ago

useScript is available from the @nuxt/scripts install

  • how you deploy (static or SSR)

I have two sites with the same issue. One is built with cloudflare-pages-static Nitro preset (so, static), and the other with cloudflare-module preset (SSR).

  • how you load Cloudflare Web Analytics with the global loader (nuxt.config.ts entry)

I actually tried a few different ways.

Via the nuxt.config.ts:

$production: {
  scripts: {
    registry: {
      cloudflareWebAnalytics: true,
    },
  },
}

And then separately, I also tried using the composable in app.vue in the root of the script setup block:

const { token } = useRuntimeConfig().public.scripts.cloudflareWebAnalytics
if (token) {
  useScriptCloudflareWebAnalytics({
    token,
    scriptOptions: {
      trigger: 'onNuxtReady',
    },
  })
}
vejja commented 2 weeks ago

In the raw HTML that you get on your home page, can you see the https://static.cloudflareinsights.com/beacon.min.js script src somewhere ?

adamdehaven commented 2 weeks ago

Yes:

<script data-onload="" data-onerror="" defer="" fetchpriority="low" referrerpolicy="no-referrer" src="https://static.cloudflareinsights.com/beacon.min.js" data-cf-beacon="{&quot;token&quot;:&quot;<TOKEN>&quot;,&quot;spa&quot;:true}" data-hid="136af0b"></script>
vejja commented 2 weeks ago

The script is being inserted server-side, this is incompatible with CSP. Which causes the error.

There seems to be an option for ´client’ mode with the composable : https://scripts.nuxt.com/scripts/analytics/cloudflare-web-analytics#usescriptcloudflarewebanalytics

but it’s not 100% clear to me how to disable server-side injection from reading the docs

I’m offline right now and cannot assist better immediately I’m afraid. Maybe you can ask the team @nuxt/scripts why the script is being inserted server-side, they will know

copying @harlan-zw on this one

adamdehaven commented 2 weeks ago

I was able to get this working by utilizing useHead (available directly in Nuxt) without the need for @nuxt/scripts.

I simply placed this into my app.vue at the project root:

useHead({
  script: [{
    src: 'https://static.cloudflareinsights.com/beacon.min.js',
    'data-cf-beacon': JSON.stringify({
      token: '<TOKEN>',
      spa: true,
    }),
    defer: true,
    tagPosition: 'bodyClose',
    key: 'cloudflare-web-analytics',
  }],
}, { mode: 'client' })

@harlan-zw I attempted multiple solutions with @nuxt/scripts (and the useScriptCloudflareWebAnalytics composable), even trying to pass trigger: 'client' and a couple other variations; however, it never seemed to respect the client config setting.


Separately, @vejja, this required a manual setup (i.e. manually loading the script here); however, they also have an "automated setup" where they simply inject the HTML into the site.

With the strict-dynamic rule in place, their added script tag is still subject to the original error in this Issue. Is there a workaround in my CSP settings if I want to allow an "integration" (e.g. Cloudflare Web Analytics) to add its own script on the page without requiring this manual setup?

vejja commented 2 weeks ago

If the script src tag is delivered as part of the original HTML, it will be denied by CSP. This is the whole point of CSP.

However if the script src tag is added dynamically by the client after the original HTML is received, it will be allowed - provided the ‘strict-dynamic’ policy is set and the ‘useHead’ or ´useScript’ composables are used to insert the script.

So : if anyone injects the Githubissues.

  • Githubissues is a development platform for aggregating issues.