image icon indicating copy to clipboard operation
image copied to clipboard

Support for nonce or hash value [enhancment]

Open rahul37865 opened this issue 2 years ago • 15 comments

Recently I added Nuxt Security in a Project where i am fetching data from an API URL http://127.0.0.1:8000/api/blog/post/27 i am implementing Nuxt Image Module and While displaying image <NuxtImg crossorigin="anonymous" :src="postDetail.post_image" /> It throws an error (only on Firefox, doesn't throw error on chrome and edge)

Content-Security-Policy: The page’s settings blocked the loading of a resource at inline (“script-src-attr”).
Source: this.setAttribute('data-error', 1)

which i fixed using below settings

security: {
  headers: {
    contentSecurityPolicy: {
      'img-src': ["'self'", 'data:', 'http://127.0.0.1:8000'],
      'script-src-attr': ["'unsafe-inline'"],        // Added this
    },
  },
},

But it is considered a security risk, as it can open the door to XSS attacks. It's generally recommended to avoid using 'unsafe-inline' whenever possible. I discussed this matter with Nuxt Security and they recommend to use nonce or hash value. They also said that unfortunately Nuxt Image doesnt support nonce or hash so there is no secure way. You can find the discussion here https://github.com/Baroshem/nuxt-security/issues/218

rahul37865 avatar Sep 27 '23 07:09 rahul37865

I've created a reference implementation on the nonce propagation here https://github.com/trijpstra-fourlights/nuxt-image/tree/feat/add-nonce-support

I'll test a bit more with this and create a PR for it when it's good enough.

Hash support is not really applicable here AFAICT, as it requires computing a hash for the known script and whitelisting that in your CSP.

trijpstra-fourlights avatar Sep 27 '23 08:09 trijpstra-fourlights

I am really looking forward to this feature as it would enable working well with Nuxt Image & Nuxt Security :)

Baroshem avatar Sep 28 '23 11:09 Baroshem

Hey @pi0 could you take a look at it? It would be really useful to have this feature. Thomas is doing an amazing work in the development of Nuxt Security features and now prepared a feature for Nuxt Image that would help not only in the case of Nuxt security, but also for other projects as well :)

Baroshem avatar Sep 28 '23 11:09 Baroshem

Yes Team Please do check and if everything looks good then proceed to merge.

rahul37865 avatar Sep 28 '23 16:09 rahul37865

@pi0

I think this issue could be closed as the PR was merged :)

Baroshem avatar Oct 04 '23 07:10 Baroshem

I don't think that this issue is resolved fully yet. The this.setAttribute('data-error', 1) inline script is still inline onerror and results in the given error. It appears to me that the nonce is currently only added to the preload header.

dargmuesli avatar Oct 27 '23 00:10 dargmuesli

Specifzing the nonce attribute on <NuxtImg> will produce the following markup:

<img onError="this.setAttribute('data-error', 1)" nonce="foo">

This will not work, even when the page specifies script-src-attr: 'nonce-foo'. It seems like nonces are not supported for inline event handlers at all. Generally, inline event handlers are considered bad practice and are hard to use with CSP. .addEventListener() should be used instead.

Note that you only see a CSP error, when the onerror script actually gets executed, which is why some may thought that their CSP is working correctly. To test, specify an invalid image source.

P4sca1 avatar Jul 22 '24 11:07 P4sca1

Note that the error only occurs during SSR. The reason is the following line: https://github.com/nuxt/image/blob/2b6a877a9d61923d22602707a5568a3c0deb6818/src/runtime/components/nuxt-img.ts#L153

P4sca1 avatar Jul 22 '24 12:07 P4sca1

A workaround could be to attach the event listener in the onMounted hook using a ref. However, some error event may be missed in that case due to timing issues. Maybe setting the image source could be delayed until after the event listener is attached in the onMounted hook.

P4sca1 avatar Jul 22 '24 12:07 P4sca1

Looking for this support, any plans to address soon? Currently having issues when trying to implement a CSP policy:

Content-Security-Policy: The page’s settings blocked an event handler (script-src-attr) from being executed because it violates the following directive: “script-src-attr 'self'” Source: this.setAttribute('data-error', 1) 

surgiie avatar Jul 22 '24 21:07 surgiie

I currently experience the exact same issue, any updates on this topic?

LeeKrane avatar Sep 17 '24 10:09 LeeKrane

I currently experience the exact same issue, any updates on this topic?

did you find any solution to this? i am also experiencing the exact same issue

tahirmahmudzade avatar Oct 22 '24 06:10 tahirmahmudzade

Please refrain from "me too" posts that don't add further to the solution process. Comments notify all persons interested and take time away from contributing.

dargmuesli avatar Oct 22 '24 10:10 dargmuesli

Not sure if this would work on a PR but I was able to patch the issue by making the following changes to the node_modules/@nuxt/image/dist/runtime/components/NuxtImg.vue file. I used bun patch but you could also use NPM patch-package.

<template>
  <img
    /* ... */
    v-bind="{
      /* Remove next line */
      ...isServer ? { onerror: 'this.setAttribute(\'data-error\', 1)' } : {},
      ...imgAttrs,
      ...attrs,
    }"
    :src="src"
  >
  <slot
    /* We use the ref for this too */
    ref="imgEl"
    v-else
    v-bind="{
      imgAttrs: {
        /* Remove next line */
        ...isServer ? { onerror: 'this.setAttribute(\'data-error\', 1)' } : {},
        ...imgAttrs,
        ...attrs,
      },
      isLoaded: placeholderLoaded,
      src,
    }"
  />
</template>

<script setup lang="ts">
  /* ... */
  if (isServer && imgEl.value) {
    imgEl.value.addEventListener('error', () => imgEl.value?.setAttribute('data-error', '1'));
  }
  /* ...*/
</script>

This is my patch file for v1.9.0

# patches/@nuxt%[email protected]

diff --git a/dist/runtime/components/NuxtImg.vue b/dist/runtime/components/NuxtImg.vue
index 4625e1ef0028dd2470ed832106cea08e9adcac38..8f6ae6df24766e40b33a65977e0c2313e99a1f9b 100644
--- a/dist/runtime/components/NuxtImg.vue
+++ b/dist/runtime/components/NuxtImg.vue
@@ -4,7 +4,6 @@
     ref="imgEl"
     :class="props.placeholder && !placeholderLoaded ? props.placeholderClass : undefined"
     v-bind="{
-      ...isServer ? { onerror: 'this.setAttribute(\'data-error\', 1)' } : {},
       ...imgAttrs,
       ...attrs,
     }"
@@ -12,8 +11,8 @@
   >
   <slot
     v-else
+    ref="imgEl"
     v-bind="{
-      ...isServer ? { onerror: 'this.setAttribute(\'data-error\', 1)' } : {},
       imgAttrs: {
         ...imgAttrs,
         ...attrs,
@@ -118,6 +117,9 @@ const mainSrc = computed(() =>
 
 const src = computed(() => placeholder.value ? placeholder.value : mainSrc.value)
 
+if(import.meta.server && imgEl.value) {
+  imgEl.value.addEventListener('error', () => imgEl.value?.setAttribute('data-error', '1'))
+}
 if (import.meta.server && props.preload) {
   const isResponsive = Object.values(sizes.value).every(v => v)
 

maucastrojm avatar Mar 17 '25 23:03 maucastrojm

Not sure if this would work on a PR but I was able to patch the issue by making the following changes to the node_modules/@nuxt/image/dist/runtime/components/NuxtImg.vue file. I used bun patch but you could also use NPM patch-package.

This is my patch file for v1.9.0

patches/@nuxt%[email protected]

diff --git a/dist/runtime/components/NuxtImg.vue b/dist/runtime/components/NuxtImg.vue index 4625e1ef0028dd2470ed832106cea08e9adcac38..8f6ae6df24766e40b33a65977e0c2313e99a1f9b 100644 --- a/dist/runtime/components/NuxtImg.vue +++ b/dist/runtime/components/NuxtImg.vue @@ -4,7 +4,6 @@ ref="imgEl" :class="props.placeholder && !placeholderLoaded ? props.placeholderClass : undefined" v-bind="{

  •  ...isServer ? { onerror: 'this.setAttribute(\'data-error\', 1)' } : {},
     ...imgAttrs,
     ...attrs,
    
    }" @@ -12,8 +11,8 @@
    <slot v-else
  • ref="imgEl" v-bind="{
  •  ...isServer ? { onerror: 'this.setAttribute(\'data-error\', 1)' } : {},
     imgAttrs: {
       ...imgAttrs,
       ...attrs,
    

@@ -118,6 +117,9 @@ const mainSrc = computed(() =>

const src = computed(() => placeholder.value ? placeholder.value : mainSrc.value)

+if(import.meta.server && imgEl.value) {

  • imgEl.value.addEventListener('error', () => imgEl.value?.setAttribute('data-error', '1')) +} if (import.meta.server && props.preload) { const isResponsive = Object.values(sizes.value).every(v => v)

I dont see any open PRs for this, worth opening i think?

surgiie avatar May 06 '25 13:05 surgiie