How to pass env variables into `contentSecurityPolicy`
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:
- Configure everything through env variable (such that no hard URLs are in the codebase)
- 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?
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.tsstarting 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 thenuxt-security:routeRuleshook 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.envwhich is specific to NodeJS, but will/should fail in other environments. However because your Nuxt app is built with Node,process.envshould 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.IMAGEBASEURLis not undefined?
Let me know
@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.
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.tswithout 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
- Ideally, a simple repro would be great
- If not possible, explain how you load your env variables (both in dev and prod modes)
- 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
@kalnode
Have you tried the solutions from @vejja ? :)
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?
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