Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions packages/plugins/apps/src/vite/build-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,121 @@ describe('getBaseBackendBuildConfig', () => {
rmSync(workingDir);
}
});

// Regression coverage: Vite's loadEnv() copies any VITE_-prefixed key straight out of the real
// process.env into import.meta.env, and its `define` plugin statically inlines that value into
// the built output at build time — completely bypassing runWithScopedEnv's runtime scoping,
// which only wraps module execution, never this bundling step.
test('Should not inline a VITE_-prefixed real process.env value into the built backend function', async () => {
const seed = `build-config-env-leak-${Date.now()}`;
const workingDir = getTempWorkingDir(seed);
const secretKey = 'VITE_DD_TEST_REAL_SECRET';
const secretValue = 'sk_should_never_be_inlined';
const originalValue = process.env[secretKey];
process.env[secretKey] = secretValue;

try {
const absolutePath = `${workingDir}/src/readsViteEnv.backend.ts`;

outputFileSync(
absolutePath,
`
export async function readsViteEnv() {
return import.meta.env.${secretKey};
}
`,
);

const virtualId = 'virtual:dd-backend-test:readsViteEnv';
const virtualContent = `import { readsViteEnv } from ${JSON.stringify(absolutePath)};\nexport async function main($) { return await readsViteEnv(); }`;
const baseConfig = getBaseBackendBuildConfig(
workingDir,
{ [virtualId]: virtualContent },
[],
);

const result = await build({
...baseConfig,
build: {
...baseConfig.build,
write: false,
rollupOptions: {
...baseConfig.build.rollupOptions,
input: virtualId,
output: baseConfig.build.rollupOptions.output,
},
},
});

const output = Array.isArray(result) ? result[0] : result;
if (!('output' in output)) {
throw new Error('Unexpected vite.build result');
}
const chunk = output.output[0];
const code = chunk.type === 'chunk' ? chunk.code : '';

expect(code).not.toContain(secretValue);
} finally {
if (originalValue === undefined) {
delete process.env[secretKey];
} else {
process.env[secretKey] = originalValue;
}
rmSync(workingDir);
}
});

// envPrefix: [] alone only blocks process.env — a secret that exists solely in a build root's
// own .env file, never set on process.env at all, needs envFile: false to stay unread.
test('Should not inline a VITE_-prefixed secret that exists only in a build root .env file', async () => {
const seed = `build-config-dotenv-leak-${Date.now()}`;
const workingDir = getTempWorkingDir(seed);
const secretValue = 'sk_should_never_be_inlined_from_dotenv';

try {
outputFileSync(`${workingDir}/.env`, `VITE_DD_TEST_DOTENV_SECRET=${secretValue}\n`);

const absolutePath = `${workingDir}/src/readsDotenv.backend.ts`;
outputFileSync(
absolutePath,
`
export async function readsDotenv() {
return import.meta.env.VITE_DD_TEST_DOTENV_SECRET;
}
`,
);

const virtualId = 'virtual:dd-backend-test:readsDotenv';
const virtualContent = `import { readsDotenv } from ${JSON.stringify(absolutePath)};\nexport async function main($) { return await readsDotenv(); }`;
const baseConfig = getBaseBackendBuildConfig(
workingDir,
{ [virtualId]: virtualContent },
[],
);

const result = await build({
...baseConfig,
build: {
...baseConfig.build,
write: false,
rollupOptions: {
...baseConfig.build.rollupOptions,
input: virtualId,
output: baseConfig.build.rollupOptions.output,
},
},
});

const output = Array.isArray(result) ? result[0] : result;
if (!('output' in output)) {
throw new Error('Unexpected vite.build result');
}
const chunk = output.output[0];
const code = chunk.type === 'chunk' ? chunk.code : '';

expect(code).not.toContain(secretValue);
} finally {
rmSync(workingDir);
}
});
});
7 changes: 7 additions & 0 deletions packages/plugins/apps/src/vite/build-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ export function getBaseBackendBuildConfig(
} {
return {
configFile: false,
// configFile: false only skips loading a vite.config.js — it does not disable Vite's
// separate .env-file/import.meta.env machinery, which otherwise copies any VITE_-prefixed
// key straight out of the real process.env and statically inlines it into the built
// backend function. envPrefix: [] blocks that copy; envFile: false additionally stops a
// secret set only in the build root's own .env file from being read at all.
envFile: false,
Comment thread
tyffical marked this conversation as resolved.
envPrefix: [],
Comment thread
Copilot marked this conversation as resolved.
root,
logLevel: 'silent',
build: {
Expand Down
Loading
Loading