Skip to content

Commit 1a15460

Browse files
authored
feat: server.fs.deny support (#5378)
1 parent d5e51f4 commit 1a15460

File tree

9 files changed

+60
-6
lines changed

9 files changed

+60
-6
lines changed

docs/config/index.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,15 @@ createServer()
593593
})
594594
```
595595

596+
### server.fs.deny
597+
598+
- **Experimental**
599+
- **Type:** `string[]`
600+
601+
Blocklist for sensitive files being restricted to be served by Vite dev server.
602+
603+
Default to `['.env', '.env.*', '*.{pem,crt}']`.
604+
596605
### server.origin
597606

598607
- **Type:** `string`

packages/playground/fs-serve/__tests__/fs-serve.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ describe('main', () => {
4141
test('nested entry', async () => {
4242
expect(await page.textContent('.nested-entry')).toBe('foobar')
4343
})
44+
45+
test('nested entry', async () => {
46+
expect(await page.textContent('.nested-entry')).toBe('foobar')
47+
})
48+
49+
test('denied', async () => {
50+
expect(await page.textContent('.unsafe-dotenv')).toBe('404')
51+
})
4452
} else {
4553
test('dummy test to make jest happy', async () => {
4654
// Your test suite must contain at least one test.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
KEY=safe
1+
KEY=unsafe

packages/playground/fs-serve/root/src/index.html

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ <h2>Unsafe /@fs/ Fetch</h2>
2323
<h2>Nested Entry</h2>
2424
<pre class="nested-entry"></pre>
2525

26+
<h2>Denied</h2>
27+
<pre class="unsafe-dotenv"></pre>
28+
2629
<script type="module">
2730
import '../../entry'
2831
import json, { msg } from '../../safe.json'
@@ -31,7 +34,7 @@ <h2>Nested Entry</h2>
3134
text('.named', msg)
3235

3336
// inside allowed dir, safe fetch
34-
fetch('/src/.env')
37+
fetch('/src/safe.txt')
3538
.then((r) => {
3639
text('.safe-fetch-status', r.status)
3740
return r.text()
@@ -41,7 +44,7 @@ <h2>Nested Entry</h2>
4144
})
4245

4346
// outside of allowed dir, treated as unsafe
44-
fetch('/.env')
47+
fetch('/unsafe.txt')
4548
.then((r) => {
4649
text('.unsafe-fetch-status', r.status)
4750
return r.text()
@@ -76,7 +79,16 @@ <h2>Nested Entry</h2>
7679
console.error(e)
7780
})
7881

82+
// .env, denied by default
83+
fetch('/@fs/' + ROOT + '/root/.env')
84+
.then((r) => {
85+
text('.unsafe-dotenv', r.status)
86+
})
87+
.catch((e) => {
88+
console.error(e)
89+
})
90+
7991
function text(sel, text) {
8092
document.querySelector(sel).textContent = text
8193
}
82-
</script>
94+
</script>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
KEY=safe

packages/vite/src/node/server/index.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,18 @@ export interface FileSystemServeOptions {
162162
* @experimental
163163
*/
164164
allow?: string[]
165+
166+
/**
167+
* Restrict accessing files that matches the patterns.
168+
*
169+
* This will have higher priority than `allow`.
170+
* Glob patterns are supported.
171+
*
172+
* @default ['.env', '.env.*', '*.crt', '*.pem']
173+
*
174+
* @experimental
175+
*/
176+
deny?: string[]
165177
}
166178

167179
/**
@@ -690,6 +702,7 @@ export function resolveServerOptions(
690702
): ResolvedServerOptions {
691703
const server = raw || {}
692704
let allowDirs = server.fs?.allow
705+
const deny = server.fs?.deny || ['.env', '.env.*', '*.{crt,pem}']
693706

694707
if (!allowDirs) {
695708
allowDirs = [searchForWorkspaceRoot(root)]
@@ -706,7 +719,8 @@ export function resolveServerOptions(
706719
server.fs = {
707720
// TODO: make strict by default
708721
strict: server.fs?.strict,
709-
allow: allowDirs
722+
allow: allowDirs,
723+
deny
710724
}
711725
return server as ResolvedServerOptions
712726
}

packages/vite/src/node/server/middlewares/static.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
slash,
1515
isFileReadable
1616
} from '../../utils'
17+
import match from 'minimatch'
1718

1819
const sirvOptions: Options = {
1920
dev: true,
@@ -130,6 +131,8 @@ export function serveRawFsMiddleware(
130131
}
131132
}
132133

134+
const _matchOptions = { matchBase: true }
135+
133136
export function isFileServingAllowed(
134137
url: string,
135138
server: ViteDevServer
@@ -140,6 +143,9 @@ export function isFileServingAllowed(
140143
const cleanedUrl = cleanUrl(url)
141144
const file = ensureLeadingSlash(normalizePath(cleanedUrl))
142145

146+
if (server.config.server.fs.deny.some((i) => match(file, i, _matchOptions)))
147+
return false
148+
143149
if (server.moduleGraph.safeModulesPath.has(file)) return true
144150

145151
if (server.config.server.fs.allow.some((i) => file.startsWith(i + '/')))

packages/vite/types/shims.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,11 @@ declare module 'rollup-plugin-web-worker-loader' {
9696
}
9797

9898
declare module 'minimatch' {
99-
function match(path: string, pattern: string): boolean
99+
function match(
100+
path: string,
101+
pattern: string,
102+
options?: { matchBase?: boolean }
103+
): boolean
100104
export default match
101105
}
102106

0 commit comments

Comments
 (0)