Skip to content

Commit 74fb28b

Browse files
brenelzclaude
andauthored
fix(start-env): prefixed server keys are a config-time error — Vite bakes every VITE_-prefixed var into the browser's import.meta.env regardless of schema side, so a server-declared VITE_* secret leaked silently through Vite's own channel (the leak scan only watches the virtual server module's values); the prefix rule is now enforced both ways, with a fixture and guard assertion in the start-env suite (#302)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 84a4cab commit 74fb28b

4 files changed

Lines changed: 44 additions & 0 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'vite-plugin-solid': patch
3+
---
4+
5+
`start.env` now rejects `server` schema keys that carry the public env prefix at config time. Vite bakes every `VITE_`-prefixed variable (or whatever `envPrefix` selects) into the browser's `import.meta.env` regardless of which side of the schema declares it, so `server: { VITE_API_SECRET: ... }` silently shipped the secret to every client through Vite's own channel — with no diagnostics, since the leak scan only watches the virtual server module's values. The prefix rule was previously enforced one-way (client keys must have it); the reverse guard now fails fast with a rename message, mirroring the existing client-side guard.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { z } from 'zod';
2+
3+
// Fixture for the config-time server-prefix guard
4+
// (ENV_SCHEMA=./env.serverprefix.ts): Vite bakes every VITE_-prefixed var
5+
// into the browser's import.meta.env regardless of schema side, so a
6+
// prefixed key under `server` can never stay secret and must be rejected
7+
// before anything builds.
8+
export default {
9+
server: {
10+
VITE_API_SECRET: z.string().min(8),
11+
},
12+
};

examples/start-env/test/run.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,12 @@ async function guardsMode() {
365365

366366
out = await build({ ENV_SCHEMA: './env.badprefix.ts' }).catch((e) => e.message);
367367
record('guards', 'prefix', 'non-VITE_ client key is a config-time error', /must carry the public env prefix/.test(out) && /APP_NAME/.test(out), out.slice(0, 400));
368+
369+
// The reverse: Vite bakes every VITE_-prefixed var into the browser's
370+
// import.meta.env regardless of schema side, so a prefixed SERVER key
371+
// is a leak the moment it exists — reject it before anything builds.
372+
out = await build({ ENV_SCHEMA: './env.serverprefix.ts' }).catch((e) => e.message);
373+
record('guards', 'prefix', 'VITE_-prefixed server key is a config-time error', /cannot keep it secret/.test(out) && /VITE_API_SECRET/.test(out), out.slice(0, 400));
368374
}
369375

370376
async function prodMode() {

src/start-env.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,27 @@ function assertSchemaShape(
247247
);
248248
}
249249
}
250+
// The reverse guard: Vite itself bakes every prefixed variable into
251+
// `import.meta.env` for the browser, so declaring one under `server`
252+
// cannot keep it secret — it leaks through Vite's channel with no
253+
// diagnostics from this plugin's leak scan (which only watches the
254+
// virtual server module's values).
255+
for (const key of Object.keys(typed.server ?? {})) {
256+
const prefix = envPrefixes.find((p) => key.startsWith(p));
257+
if (prefix) {
258+
const bare = key.slice(prefix.length);
259+
throw new Error(
260+
`[vite-plugin-solid] server env var "${key}" in ${envFile} carries the public ` +
261+
`env prefix "${prefix}". Vite exposes every "${prefix}"-prefixed variable to ` +
262+
`the browser through import.meta.env no matter which side declares it, so a ` +
263+
`\`server\` entry cannot keep it secret. ` +
264+
(bare
265+
? `Rename it to "${bare}" (in the schema and in your .env/environment), or `
266+
: `Rename it without the prefix, or `) +
267+
`move it to \`client\` if it is public.`,
268+
);
269+
}
270+
}
250271
return typed;
251272
}
252273

0 commit comments

Comments
 (0)