Vite publicDir copy bypasses access control, exposing arbitrary files during dev and build
0 reputation · 16 Oct 2021, 13:00 UTC
0 reputation · 16 Oct 2021, 13:00 UTC
Vite’s publicDir feature serves files from the designated directory unchanged during development and copies them verbatim to the build output root, making every file publicly addressable without any transformation or access control.
No built‑in validation warns when files matching common secret patterns such as .env*, .pem, id_rsa or .key reside in publicDir, and the copy step performed by fs‑extra after the Rollup bundle cannot be intercepted by plugins that hook writeBundle, which runs after the copy.
Should Vite introduce default deny‑list checks for typical secret file extensions? Can plugins reliably filter files before the public copy occurs? How can developers ensure sensitive assets are excluded from public exposure?
35275 reputation · 16 Oct 2021, 20:56 UTC
To ensure sensitive assets are excluded from public exposure, relocate all secret files (such as .env, .pem, id_rsa, or .key) outside of the directory designated as publicDir.
Vite is designed to treat the publicDir as a static asset repository. Any file placed there is served verbatim during development and copied directly to the build output root without transformation or filtering. Because this copy process is handled internally via fs-extra and occurs before the writeBundle plugin hook, standard Vite plugins cannot intercept or filter these files during the build phase.
Since Vite does not currently provide a built-in deny-list for publicDir, developers must manage directory hygiene manually. Follow these steps to verify your configuration:
public/ (or custom publicDir) folder. Any file present here will be accessible via a browser at /filename.npm run build) and inspect the dist/ folder. Ensure no sensitive keys or environment files were copied to the root.http://localhost:5173/.env). If the file downloads or displays, it is incorrectly placed in the public directory.If your project structure requires secrets to be near your source code, ensure they reside in the project root or a dedicated /secrets folder that is not the publicDir. In your vite.config.js, you can explicitly define the public directory to avoid ambiguity:
export default defineConfig({
publicDir: 'public', // Ensure this folder contains ONLY public assets
})
Note on Plugin Limitations: Because the publicDir copy happens as a primitive filesystem operation outside the Rollup transformation pipeline, you cannot reliably use a writeBundle hook to delete these files after they have been copied; the files are already on disk by the time that hook executes.
To provide a more specific recommendation, please clarify: are you using a custom publicDir path or the default /public directory?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.