withastro/astro

Unable to resolve import.meta.env.xxx

Closed this issue · 20 comments

eya46 commented

Astro Info

PS F:\eya46eya46> astro info
Astro                    v5.0.2
Node                     v22.12.0
System                   Windows (x64)
Package Manager          unknown // pnpm
Output                   server
Adapter                  @astrojs/node
Integrations             @astrojs/tailwind

If this issue only occurs in one browser, which browser is a problem?

No response

Describe the Bug

my code src/utils/wakatime.ts

The following content is from a translation.

Reproduce the bug(?)

// astro.config.mjs
export default defineConfig({
  output: "server",
  adapter: node({
    mode: "standalone",
  }),
}

// any where
import.meta.env.xxx

It works correctly when using astro dev, but after building, it doesn't work properly when running astro preview or node dist/server/entry.mjs.


In the new version, the source code const WAKATIME_TOKEN = import.meta.env.WAKATIME_TOKEN; is compiled into const WAKATIME_TOKEN = process.env.WAKATIME_TOKEN;.

Previously, import.meta.env.WAKATIME_TOKEN would be directly replaced with the actual value. However, now it is replaced with process.env.xxx, making it unusable.

The .env file in the root directory is properly configured.
The error message is as follows:

// dist/server/pages/index.astro.mjs
const WAKATIME_TOKEN = process.env.WAKATIME_TOKEN;
if (!WAKATIME_TOKEN) throw new Error("WAKATIME_TOKEN is not defined");
15:19:11 [ERROR] Error: WAKATIME_TOKEN is not defined
    at file:///F:/eya46eya46/dist/server/pages/index.astro.mjs:121:28
    at ModuleJob.run (node:internal/modules/esm/module_job:271:25)
    at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:547:26)
    at async AppPipeline.getModuleForRoute (file:///F:/eya46eya46/dist/server/chunks/_@astrojs-ssr-adapter_Boj1eyKp.mjs:2267:16)
    at async NodeApp.render (file:///F:/eya46eya46/dist/server/chunks/_@astrojs-ssr-adapter_Boj1eyKp.mjs:2447:19)
    at async file:///F:/eya46eya46/dist/server/chunks/_@astrojs-ssr-adapter_Boj1eyKp.mjs:2806:30

What's the expected result?

Able to correctly handle import.meta.env.xxx or .env files.

Link to Minimal Reproducible Example

https://stackblitz.com/edit/withastro-astro-drnr9z?file=src%2Fpages%2Findex.astro

Participation

  • I am willing to submit a pull request for this issue.
eya46 commented

build by "astro": "^4.16.8",:
image

Thank you for reporting this, we're trying to understand if the issue is related to Astro or Vite specifically. It's probably vite, but we'll keep you posted

I was just about to report this one too. This is also happening with the Cloudflare adapter. Running with astro dev is totally fine, but building and deploying / running through wrangler will also replace the variable with process.env.VARIABLE_NAME instead of the actual value of the variable.

It seems like prefixing the variable with PUBLIC_ will allow it to work again, so seems like it might be something weird with server variables?

Reproducible Example: https://stackblitz.com/edit/github-w9xio1?file=src%2Fpages%2Findex.astro

Dist output example

bluwy commented

This seems to be the same issue as this behaviour currently where we mutate process.env:

const loadedEnv = loadEnv(mode, fileURLToPath(settings.config.root), '');
for (const [key, value] of Object.entries(loadedEnv)) {
if (value !== undefined) {
process.env[key] = value;
}
}

which we tried to fix before (#12227) but reverted. cc @florian-lefebvre

The issue is that since the new astro:env feature loads the .env files to process.env, it kicks in a different behaviour in the import.meta.env.* handling in Astro.

for (const key in fullEnv) {
// Ignore public env var
if (isValidIdentifierRe.test(key) && envPrefixes.every((prefix) => !key.startsWith(prefix))) {
if (typeof process.env[key] !== 'undefined') {
let value = process.env[key];
// Replacements are always strings, so try to convert to strings here first
if (typeof value !== 'string') {
value = `${value}`;
}
// Boolean values should be inlined to support `export const prerender`
// We already know that these are NOT sensitive values, so inlining is safe
if (value === '0' || value === '1' || value === 'true' || value === 'false') {
privateEnv[key] = value;
} else {
privateEnv[key] = `process.env.${key}`;
}
} else {
privateEnv[key] = JSON.stringify(fullEnv[key]);
}
}
}

Aside from the behaviour being not the most intuitive and is hard to understand (which is where astro:env comes in), I guess we still have to preserve the old env support and not break it.

eya46 commented

may be...

https://vite.dev/guide/env-and-mode.html#env-files

To prevent accidentally leaking env variables to the client, only variables prefixed with VITE_ are exposed to your Vite-processed code. e.g. for the following env variables:

To add to this discussion, my environmental variables did not break after the v5 upgrade on my site dawnforge.app

Not using astro:env just the standard import.meta.env syntax, hosted on netlify. They are server variables, not prefixed with PUBLIC

I upgraded to v5 and mine env variables breaks. I was using them with import.meta.env for example SECRET_PASSWORD and it stopped working after upgrade.

I had to rewrite them to astro:env/server with access public. The access secret not working in production too. I have hosted it on VPS with Fastify and Nginx with reverse proxy.

The access secret not working in production too.

@Fakerko can you file an issue with a reproduction?

@ematipico minimal reproduction for both, import.meta.env and astro:env/server, here: https://codesandbox.io/p/sandbox/nostalgic-hoover-kvycz3

Created .env file, updated astro.config.mjs and /src/pages/index.astro

if you run it in dev mode npm run dev its working, if you build it npm run build and run it with npm run preview, it stop working.

The latter is not the same issue, and is not a bug. You'd need to run SECRET_PASSWORD=whatever npm run preview

The latter is not the same issue, and is not a bug. You'd need to run SECRET_PASSWORD=whatever npm run preview

But that misses the point of using the .env file, doesn't it?

If I have 10 or 20 SECRET variables and I have to specify them every production run, not only will it be a mess, but I'll also have to have secret variables in the package.json that gets committed to GIT.

Before I upgraded to version 5, everything worked fine using import.meta.env directly from the .env file.

@Fakerko

Wouldn't import.meta.env statically replace the secrets during the build?

which are statically replaced at build time

This means that, so far, all your 10/20 secrets have been replaced during the build, so they are in clear inside your built files. If that's what you want, then when using envField, you must define your secret with public access.

It's possible I might have misunderstood something. Let's keep the discussion going .

That's exactly what I did, I changed access to public. To make it work.

So if I understand it correctly, if I use envField with context "server" and access: "public", the content of the variable is on the server, but still someone can access it? Or am I understanding it wrong and it's fine?

Thanks for the explanation.

but still someone can access it? Or am I understanding it wrong and it's fine?

Yes, the value is statically replaced during the build, which means that the final, compiled code will have the value of the environment variable in the code, e.g.

That's how you run your code during the build

PUBLIC_SECRET=token123 astro build

Somewhere, your bundled JS files, will have this:

export const PUBLIC_SECRET = "token123"

Instead, if your secret contains sensitive information, make the variablesecret, and don't use import.meta.env. Eventually, when you run the app, you need to provide it:

SUPER_HIDDEN_SECRET=token123 node ./dist/server/entry.mjs

Okay, thank you for the explanation.

So if I use context: "server" and access: "public", the contents of the variable are contained in a bundle on the server in the /dist/server/...

One last question for my clarification:
Is there a real possibility that someone from the outside, for example a site visitor, can get to this data or is this data only accessible on the server to those (those who have access to the server, i.e. the person who has access to the VPS for example)?

Maybe this table should clear things up:

Client (browser) Backend (server)
client + public Visible Visible
server + public Not visible Visible
server + secret Not visible Not Visible

So, if a contractor who's external to your company has access to your server (VPS, SSH, etc.) and can read the emitted files by your application, they will be able to read the variables:

  • client + public
  • server + public

@eya46 Can you try this preview release? astro@experimental--astroenv-dev

Seems like that preview fixes this on the Cloudflare adapter for me!

Awesome! We'll most likely release it start of next week

eya46 commented

@eya46 Can you try this preview release? astro@experimental--astroenv-dev

It's working

Screenshot_2024-12-07-23-40-14-620_com.server.auditor.ssh.client-edit.jpg

Screenshot_2024-12-07-23-38-53-388_com.server.auditor.ssh.client-edit.jpg