withastro/astro · critical · AstroError
EnvPrefixConflictsWithSecret
EnvPrefixConflictsWithSecret
Error message
The following environment variables are declared with `access: "secret"` in `env.schema`, but their names match a prefix in `vite.envPrefix`, which would expose them in client-side bundles:
${conflicts.map((c) => `- ${c}`).join('\n')}
Either remove the conflicting prefixes from `vite.envPrefix`, or rename these variables to use a prefix not in `vite.envPrefix`. What it means
astro:env validates your config before serving or building. This error fires when a variable declared with `access: 'secret'` in `env.schema` has a name matching one of the prefixes listed in `vite.envPrefix`. Vite exposes matching variables on `import.meta.env` in client bundles, so the collision would inline a secret into shipped JavaScript. The message lists every conflicting variable name.
Solutions
- Rename the secret variables so they no longer start with any vite.envPrefix prefix.
- Remove the conflicting prefixes from vite.envPrefix and read those values through `astro:env/server` instead of import.meta.env.
- If a variable genuinely must reach the browser it is not a secret - declare it `access: 'public'` in env.schema.
Example fix
// before - astro.config.mjs
export default defineConfig({
vite: { envPrefix: ['SECRET_'] },
env: { schema: { SECRET_API_KEY: envField.string({ access: 'secret' }) } },
});
// after
export default defineConfig({
env: { schema: { SECRET_API_KEY: envField.string({ access: 'secret' }) } },
});
// read server-side: import { SECRET_API_KEY } from 'astro:env/server'; Defensive patterns
Strategy: validation
Validate before calling
// keep vite.envPrefix and env.schema disjoint for secrets
// env.schema keys with access 'secret' must not start with any envPrefix entry
const envPrefix = ['PUBLIC_'];
const schema = { SECRET_API_KEY: 'secret', PUBLIC_SITE_URL: 'public' };
const conflicts = Object.entries(schema)
.filter(([k, access]) => access === 'secret' && envPrefix.some((p) => k.startsWith(p)))
.map(([k]) => k);
if (conflicts.length) throw new Error('secret/prefix conflicts: ' + conflicts.join(', ')); Prevention
- Prefer astro:env (env.schema + astro:env/server) over import.meta.env for secrets.
- Keep vite.envPrefix narrow; never use an empty-string prefix in a project that declares secrets.
- Rely on the config-time throw: run `astro sync` or `astro build` in CI so a collision fails before deploy.
When it happens
Trigger: Setting `vite.envPrefix: ['SECRET_']` (or any prefix that matches a secret's name) while env.schema declares e.g. SECRET_API_KEY with access 'secret'; using an empty-string prefix `''`, which matches every variable name, in a project that declares any secret.
Common situations: Adopting astro:env while keeping legacy import.meta.env.SECRET_* usage via a broad envPrefix; copy-pasting Vite envPrefix configuration into an Astro project that also uses env.schema; enabling a wide prefix for convenience without checking the schema for secrets.
Related errors
- context.csp was used when rendering the route
- EnvInvalidVariables
- `security.csp. Directive` defines ` -src` resources ( ) as…
- ServerOnlyModule
- Shiki syntax highlighting uses inline styles that are not…
AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18).
Data as JSON: /api/errors/e04c66abe5345d5c.
Report an issue: GitHub.
Appendix: source
Thrown at packages/astro/src/env/validators.ts:210
const schema = config.env.schema;
const envPrefix = config.vite?.envPrefix;
// No schema or using default prefix — nothing to validate
if (Object.keys(schema).length === 0 || !envPrefix) {
return;
}
const prefixes = Array.isArray(envPrefix) ? envPrefix : [envPrefix];
const conflicts: string[] = [];
for (const [key, options] of Object.entries(schema)) {
if (options.access === 'secret' && prefixes.some((prefix) => key.startsWith(prefix))) {
conflicts.push(key);
}
}
if (conflicts.length > 0) {
throw new AstroError({
...AstroErrorData.EnvPrefixConflictsWithSecret,
message: AstroErrorData.EnvPrefixConflictsWithSecret.message(conflicts),
});
}
}
View on GitHub (pinned to 52e6c34790)