diff --git a/docs/config/index.md b/docs/config/index.md index 6bb9836e69094d..82519221acee4d 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -593,6 +593,15 @@ createServer() }) ``` +### server.fs.deny + +- **Experimental** +- **Type:** `string[]` + + Blocklist for sensitive files being restricted to be served by Vite dev server. + + Default to `['.env', '.env.*', '*.{pem,crt}']`. + ### server.origin - **Type:** `string` diff --git a/packages/playground/fs-serve/__tests__/fs-serve.spec.ts b/packages/playground/fs-serve/__tests__/fs-serve.spec.ts index c3d8ee9a9bf911..c618186b9bcd64 100644 --- a/packages/playground/fs-serve/__tests__/fs-serve.spec.ts +++ b/packages/playground/fs-serve/__tests__/fs-serve.spec.ts @@ -41,6 +41,14 @@ describe('main', () => { test('nested entry', async () => { expect(await page.textContent('.nested-entry')).toBe('foobar') }) + + test('nested entry', async () => { + expect(await page.textContent('.nested-entry')).toBe('foobar') + }) + + test('denied', async () => { + expect(await page.textContent('.unsafe-dotenv')).toBe('404') + }) } else { test('dummy test to make jest happy', async () => { // Your test suite must contain at least one test. diff --git a/packages/playground/fs-serve/root/src/.env b/packages/playground/fs-serve/root/src/.env index 3f3d0607101642..d0e0cfd28cbe57 100644 --- a/packages/playground/fs-serve/root/src/.env +++ b/packages/playground/fs-serve/root/src/.env @@ -1 +1 @@ -KEY=safe +KEY=unsafe diff --git a/packages/playground/fs-serve/root/src/index.html b/packages/playground/fs-serve/root/src/index.html index 67a2371c6b27fb..c8b294e86ab0ea 100644 --- a/packages/playground/fs-serve/root/src/index.html +++ b/packages/playground/fs-serve/root/src/index.html @@ -23,6 +23,9 @@

Unsafe /@fs/ Fetch

Nested Entry


 
+

Denied

+

+
 
\ No newline at end of file
+
diff --git a/packages/playground/fs-serve/root/src/safe.txt b/packages/playground/fs-serve/root/src/safe.txt
new file mode 100644
index 00000000000000..3f3d0607101642
--- /dev/null
+++ b/packages/playground/fs-serve/root/src/safe.txt
@@ -0,0 +1 @@
+KEY=safe
diff --git a/packages/playground/fs-serve/root/.env b/packages/playground/fs-serve/root/unsafe.txt
similarity index 100%
rename from packages/playground/fs-serve/root/.env
rename to packages/playground/fs-serve/root/unsafe.txt
diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts
index 42a1b1b5601bdb..7a3ad551fbc7d6 100644
--- a/packages/vite/src/node/server/index.ts
+++ b/packages/vite/src/node/server/index.ts
@@ -162,6 +162,18 @@ export interface FileSystemServeOptions {
    * @experimental
    */
   allow?: string[]
+
+  /**
+   * Restrict accessing files that matches the patterns.
+   *
+   * This will have higher priority than `allow`.
+   * Glob patterns are supported.
+   *
+   * @default ['.env', '.env.*', '*.crt', '*.pem']
+   *
+   * @experimental
+   */
+  deny?: string[]
 }
 
 /**
@@ -690,6 +702,7 @@ export function resolveServerOptions(
 ): ResolvedServerOptions {
   const server = raw || {}
   let allowDirs = server.fs?.allow
+  const deny = server.fs?.deny || ['.env', '.env.*', '*.{crt,pem}']
 
   if (!allowDirs) {
     allowDirs = [searchForWorkspaceRoot(root)]
@@ -706,7 +719,8 @@ export function resolveServerOptions(
   server.fs = {
     // TODO: make strict by default
     strict: server.fs?.strict,
-    allow: allowDirs
+    allow: allowDirs,
+    deny
   }
   return server as ResolvedServerOptions
 }
diff --git a/packages/vite/src/node/server/middlewares/static.ts b/packages/vite/src/node/server/middlewares/static.ts
index f4533beb64758b..48d4d6437e83f1 100644
--- a/packages/vite/src/node/server/middlewares/static.ts
+++ b/packages/vite/src/node/server/middlewares/static.ts
@@ -14,6 +14,7 @@ import {
   slash,
   isFileReadable
 } from '../../utils'
+import match from 'minimatch'
 
 const sirvOptions: Options = {
   dev: true,
@@ -130,6 +131,8 @@ export function serveRawFsMiddleware(
   }
 }
 
+const _matchOptions = { matchBase: true }
+
 export function isFileServingAllowed(
   url: string,
   server: ViteDevServer
@@ -140,6 +143,9 @@ export function isFileServingAllowed(
   const cleanedUrl = cleanUrl(url)
   const file = ensureLeadingSlash(normalizePath(cleanedUrl))
 
+  if (server.config.server.fs.deny.some((i) => match(file, i, _matchOptions)))
+    return false
+
   if (server.moduleGraph.safeModulesPath.has(file)) return true
 
   if (server.config.server.fs.allow.some((i) => file.startsWith(i + '/')))
diff --git a/packages/vite/types/shims.d.ts b/packages/vite/types/shims.d.ts
index 80a251b3f3b704..a351f6bfc81092 100644
--- a/packages/vite/types/shims.d.ts
+++ b/packages/vite/types/shims.d.ts
@@ -96,7 +96,11 @@ declare module 'rollup-plugin-web-worker-loader' {
 }
 
 declare module 'minimatch' {
-  function match(path: string, pattern: string): boolean
+  function match(
+    path: string,
+    pattern: string,
+    options?: { matchBase?: boolean }
+  ): boolean
   export default match
 }