nuxt-security icon indicating copy to clipboard operation
nuxt-security copied to clipboard

How to pass env variables into `contentSecurityPolicy`

Open kalnode opened this issue 8 months ago • 6 comments

I'm trying to pass env variables into contentSecurityPolicy. I understand there's challenges in doing this directly in nuxt.config.ts, and have read suggestions (link, link) that a Nitro server plugin could modify CSP per-route and may be able to achieve this. However, I'm unable to get this to work; looking for suggestions.

Ultimately I'm striving for:

  1. Configure everything through env variable (such that no hard URLs are in the codebase)
  2. Centralize all config (e.g. in nuxt.config.ts), however I'd live with a nitro plugin, so as long as # 1 is satisfied.

STARTING POINT

nuxt.config.ts

// Works, but need the URL to come from env variables
export default defineNuxtConfig({
    security: {
            headers: {
                contentSecurityPolicy: {                
                    // DESIRED, BUT FAILS - Ideally, we pass env value
                    'img-src': ["'self'", 'data:', process.env.SOMEURL] 
                    
                    // WORKS, BUT NOT IDEAL
                    'img-src': ["'self'", 'data:', 'https://some.website.net']
                }
            }
    }
}

TRYING NITROPLUGIN INSTEAD

Based on discussions, it's been suggested to use a custom NitroPlugin and hooks to modify CSP elsewhere, and perhaps in this way be able to pass env variables. Below I'm trying out this snippet: https://nuxt-security.vercel.app/advanced/hooks#available-hooks. The plugin works (can change CSP per-route), however I'm still unable to pass env variables.

Is there something additional needed?

server/myplugin/myplugin.ts

export default defineNitroPlugin((nitroApp) => {
    nitroApp.hooks.hook('nuxt-security:routeRules', async (appSecurityOptions) => {
        appSecurityOptions['/**'] = defuReplaceArray(
            {
                headers: {
                    contentSecurityPolicy: {                        

                        // Fails - getting undefined/null on these gets
                        //"img-src": ["'self'", 'data:', process.env.IMAGEBASEURL ]
                        //"img-src": ["'self'", 'data:', useRuntimeConfig().public.IMAGEBASEURL ]
                        
                        // Works, however still need to make this URL come from env variables
                        "img-src": ["'self'", 'data:', 'https://some.website.net' ]
                    }
                }
            },
            appSecurityOptions['/**']
        )
    })
})

Am I going in the wrong direction, or has there been new developments since the past discussions?

kalnode avatar May 05 '25 19:05 kalnode

Hey @kalnode

I'm not sure what the issue is and can only throw some basic ideas for debugging

  • A custom Nitro plugin sounds like an overkill for your use case. Normally it should work out of the box via your nuxt.config.ts starting point. As I understand you are not trying to load a dynamic configuration, your are just trying to load plain vanilla env variables so I would assume you don't need to go the nuxt-security:routeRules hook way.
  • Are you sure the issue is with Nuxt Security? Did you manage to set env variables to other modules? Or even to plain application variables: would you be able to produce a Hello World ${someEnvVariable} basic example?
  • In which environment are you running your application? I can see you are using process.env which is specific to NodeJS, but will/should fail in other environments. However because your Nuxt app is built with Node, process.env should normally work at least in dev mode, is it the case?
  • How do you store and load your env variables? This should normally be dependent upon your deployment environment. Are you able to check that process.env.IMAGEBASEURL is not undefined?

Let me know

vejja avatar May 05 '25 20:05 vejja

@vejja

I'm still gathering information and learning, however just to affirm... I am successfully using env's in other contexts within my app, both in dev and prod. For instance, I have a custom server-side mailer plugin, that takes in process.env values that differ (e.g. local vs Cloudflare).

In any case, so far it's my understanding that Nuxt modules do need to have explicit handling of env variables at runtime to satisfy the use case of "Pass env variables instead of static URLs defined in nuxt.config".

For example: Nuxt i18n

Or for Nuxt-Image, the same scenario discussed here, here and here

Or the Nuxt-gtag module, where they simply have the user use an explicit env variable name which the module checks on during runtime. This isn't practical for modules with deep options.

In these cases, it seems each module has some kind of explicit handling to allow for runtime env variables.

kalnode avatar May 08 '25 15:05 kalnode

Hi @kalnode

It is not impossible that there is a very subtle bug in our module under some corner use cases, but

  • I'm able myself to pass env variables to Nuxt Security in my own nuxt.config.ts without any issues
  • The 3 other modules you mention provide a custom runtime option for convenience only (as we do also)

FYI runtime env variables have their own tweaks. Alex Lichter has a great video that you should check out here : https://www.youtube.com/watch?v=_FYV5WfiWvs

If it does not solve your problem, I would like to better understand what's the setup in your case

  1. Ideally, a simple repro would be great
  2. If not possible, explain how you load your env variables (both in dev and prod modes)
  3. Then, for dev mode only: confirm whether your env variables values are properly loaded. Log their value in the console and confirm whether they show up.

Thanks a lot

vejja avatar May 09 '25 10:05 vejja

@kalnode

Have you tried the solutions from @vejja ? :)

Baroshem avatar May 26 '25 09:05 Baroshem

Hi, i seem to have the same issue. I think it's connected with the fact that in my case, the necessary environment variables are only available at runtime (trying to pass a hash because we use the same codebase for different environments and this is to allow for the Google Tag Manager script, that has a different id for each environment).

Anyone have any ideas how to circumvent this?

brunopbarbosa avatar Jul 25 '25 08:07 brunopbarbosa

For the record, I could achieve a dynamic runtime configuration with a roughly similar code as the first message. Note that I'm using Nuxt3.

// ~/server/plugins/security-plugin.js
import { defu } from 'defu'

/**
 * Nitro hooks so we can use runtime properties in CSP definition.
 *
 * Reference: https://nuxt-security.vercel.app/advanced/hooks#route-rules-hook
 */
export default defineNitroPlugin((nitroApp) => {
  nitroApp.hooks.hook('nuxt-security:routeRules', (appSecurityOptions) => {
    const config = useRuntimeConfig().public
    appSecurityOptions['/**'] = defu( // using defu to merge array instead of replacing
      { headers: { contentSecurityPolicy: { 'default-src': [ config.apiUrl ] } } },
      appSecurityOptions['/**']
    )
  })
})
// ~/nuxt.config.js
export default defineNuxtConfig({
  runtimeConfig: { public: { apiUrl: process.env.API_URL } },
  security: { headers: { contentSecurityPolicy: { 'default-src': [ "'self'" ] } } },
}

Then after nuxt build I run the project as:

> NUXT_PUBLIC_API_URL="https://api.example.com" node ~/.output/server/index.mjs

homersimpsons avatar Aug 20 '25 19:08 homersimpsons