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
}