Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 766947e

Browse filesBrowse files
authored
fix: backport #19965, check static serve file inside sirv (#19966)
1 parent 731b77d commit 766947e
Copy full SHA for 766947e

File tree

Expand file treeCollapse file tree

6 files changed

+146
-57
lines changed
Filter options
Expand file treeCollapse file tree

6 files changed

+146
-57
lines changed

‎docs/config/server-options.md

Copy file name to clipboardExpand all lines: docs/config/server-options.md
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,12 @@ export default defineConfig({
349349

350350
Blocklist for sensitive files being restricted to be served by Vite dev server. This will have higher priority than [`server.fs.allow`](#server-fs-allow). [picomatch patterns](https://github.com/micromatch/picomatch#globbing-features) are supported.
351351

352+
::: tip NOTE
353+
354+
This blocklist does not apply to [the public directory](/guide/assets.md#the-public-directory). All files in the public directory are served without any filtering, since they are copied directly to the output directory during build.
355+
356+
:::
357+
352358
## server.fs.cachedChecks
353359

354360
- **Type:** `boolean`

‎packages/vite/src/node/publicUtils.ts

Copy file name to clipboardExpand all lines: packages/vite/src/node/publicUtils.ts
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,8 @@ export {
2020
export { send } from './server/send'
2121
export { createLogger } from './logger'
2222
export { searchForWorkspaceRoot } from './server/searchRoot'
23-
export { isFileServingAllowed } from './server/middlewares/static'
23+
export {
24+
isFileServingAllowed,
25+
isFileLoadingAllowed,
26+
} from './server/middlewares/static'
2427
export { loadEnv, resolveEnvPrefix } from './env'

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

Copy file name to clipboardExpand all lines: packages/vite/src/node/server/middlewares/static.ts
+103-48Lines changed: 103 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import escapeHtml from 'escape-html'
77
import type { ViteDevServer } from '../..'
88
import { FS_PREFIX } from '../../constants'
99
import {
10-
fsPathFromId,
1110
fsPathFromUrl,
1211
isFileReadable,
1312
isImportRequest,
@@ -26,11 +25,16 @@ import {
2625
} from '../../../shared/utils'
2726

2827
const knownJavascriptExtensionRE = /\.[tj]sx?$/
28+
const ERR_DENIED_FILE = 'ERR_DENIED_FILE'
2929

3030
const sirvOptions = ({
31+
server,
3132
getHeaders,
33+
disableFsServeCheck,
3234
}: {
35+
server: ViteDevServer
3336
getHeaders: () => OutgoingHttpHeaders | undefined
37+
disableFsServeCheck?: boolean
3438
}): Options => {
3539
return {
3640
dev: true,
@@ -52,6 +56,22 @@ const sirvOptions = ({
5256
}
5357
}
5458
},
59+
shouldServe: disableFsServeCheck
60+
? undefined
61+
: (filePath) => {
62+
const servingAccessResult = checkLoadingAccess(server, filePath)
63+
if (servingAccessResult === 'denied') {
64+
const error: any = new Error('denied access')
65+
error.code = ERR_DENIED_FILE
66+
error.path = filePath
67+
throw error
68+
}
69+
if (servingAccessResult === 'fallback') {
70+
return false
71+
}
72+
servingAccessResult satisfies 'allowed'
73+
return true
74+
},
5575
}
5676
}
5777

@@ -63,7 +83,9 @@ export function servePublicMiddleware(
6383
const serve = sirv(
6484
dir,
6585
sirvOptions({
86+
server,
6687
getHeaders: () => server.config.server.headers,
88+
disableFsServeCheck: true,
6789
}),
6890
)
6991

@@ -104,6 +126,7 @@ export function serveStaticMiddleware(
104126
const serve = sirv(
105127
dir,
106128
sirvOptions({
129+
server,
107130
getHeaders: () => server.config.server.headers,
108131
}),
109132
)
@@ -153,16 +176,20 @@ export function serveStaticMiddleware(
153176
) {
154177
fileUrl = withTrailingSlash(fileUrl)
155178
}
156-
if (!ensureServingAccess(fileUrl, server, res, next)) {
157-
return
158-
}
159-
160179
if (redirectedPathname) {
161180
url.pathname = encodeURI(redirectedPathname)
162181
req.url = url.href.slice(url.origin.length)
163182
}
164183

165-
serve(req, res, next)
184+
try {
185+
serve(req, res, next)
186+
} catch (e) {
187+
if (e && 'code' in e && e.code === ERR_DENIED_FILE) {
188+
respondWithAccessDenied(e.path, server, res)
189+
return
190+
}
191+
throw e
192+
}
166193
}
167194
}
168195

@@ -171,7 +198,10 @@ export function serveRawFsMiddleware(
171198
): Connect.NextHandleFunction {
172199
const serveFromRoot = sirv(
173200
'/',
174-
sirvOptions({ getHeaders: () => server.config.server.headers }),
201+
sirvOptions({
202+
server,
203+
getHeaders: () => server.config.server.headers,
204+
}),
175205
)
176206

177207
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
@@ -183,24 +213,20 @@ export function serveRawFsMiddleware(
183213
// searching based from fs root.
184214
if (url.pathname.startsWith(FS_PREFIX)) {
185215
const pathname = decodeURI(url.pathname)
186-
// restrict files outside of `fs.allow`
187-
if (
188-
!ensureServingAccess(
189-
slash(path.resolve(fsPathFromId(pathname))),
190-
server,
191-
res,
192-
next,
193-
)
194-
) {
195-
return
196-
}
197-
198216
let newPathname = pathname.slice(FS_PREFIX.length)
199217
if (isWindows) newPathname = newPathname.replace(/^[A-Z]:/i, '')
200-
201218
url.pathname = encodeURI(newPathname)
202219
req.url = url.href.slice(url.origin.length)
203-
serveFromRoot(req, res, next)
220+
221+
try {
222+
serveFromRoot(req, res, next)
223+
} catch (e) {
224+
if (e && 'code' in e && e.code === ERR_DENIED_FILE) {
225+
respondWithAccessDenied(e.path, server, res)
226+
return
227+
}
228+
throw e
229+
}
204230
} else {
205231
next()
206232
}
@@ -209,56 +235,85 @@ export function serveRawFsMiddleware(
209235

210236
/**
211237
* Check if the url is allowed to be served, via the `server.fs` config.
238+
* @deprecated Use the `isFileLoadingAllowed` function instead.
212239
*/
213240
export function isFileServingAllowed(
214241
url: string,
215242
server: ViteDevServer,
216243
): boolean {
217244
if (!server.config.server.fs.strict) return true
218245

219-
const file = fsPathFromUrl(url)
246+
const filePath = fsPathFromUrl(url)
247+
return isFileLoadingAllowed(server, filePath)
248+
}
249+
250+
function isUriInFilePath(uri: string, filePath: string) {
251+
return isSameFileUri(uri, filePath) || isParentDirectory(uri, filePath)
252+
}
253+
254+
export function isFileLoadingAllowed(
255+
server: ViteDevServer,
256+
filePath: string,
257+
): boolean {
258+
const { fs } = server.config.server
220259

221-
if (server._fsDenyGlob(file)) return false
260+
if (!fs.strict) return true
222261

223-
if (server.moduleGraph.safeModulesPath.has(file)) return true
262+
if (server._fsDenyGlob(filePath)) return false
224263

225-
if (
226-
server.config.server.fs.allow.some(
227-
(uri) => isSameFileUri(uri, file) || isParentDirectory(uri, file),
228-
)
229-
)
230-
return true
264+
if (server.moduleGraph.safeModulesPath.has(filePath)) return true
265+
266+
if (fs.allow.some((uri) => isUriInFilePath(uri, filePath))) return true
231267

232268
return false
233269
}
234270

235-
export function ensureServingAccess(
271+
export function checkLoadingAccess(
272+
server: ViteDevServer,
273+
path: string,
274+
): 'allowed' | 'denied' | 'fallback' {
275+
if (isFileLoadingAllowed(server, slash(path))) {
276+
return 'allowed'
277+
}
278+
if (isFileReadable(path)) {
279+
return 'denied'
280+
}
281+
// if the file doesn't exist, we shouldn't restrict this path as it can
282+
// be an API call. Middlewares would issue a 404 if the file isn't handled
283+
return 'fallback'
284+
}
285+
286+
export function checkServingAccess(
236287
url: string,
237288
server: ViteDevServer,
238-
res: ServerResponse,
239-
next: Connect.NextFunction,
240-
): boolean {
289+
): 'allowed' | 'denied' | 'fallback' {
241290
if (isFileServingAllowed(url, server)) {
242-
return true
291+
return 'allowed'
243292
}
244293
if (isFileReadable(cleanUrl(url))) {
245-
const urlMessage = `The request url "${url}" is outside of Vite serving allow list.`
246-
const hintMessage = `
294+
return 'denied'
295+
}
296+
// if the file doesn't exist, we shouldn't restrict this path as it can
297+
// be an API call. Middlewares would issue a 404 if the file isn't handled
298+
return 'fallback'
299+
}
300+
301+
export function respondWithAccessDenied(
302+
url: string,
303+
server: ViteDevServer,
304+
res: ServerResponse,
305+
): void {
306+
const urlMessage = `The request url "${url}" is outside of Vite serving allow list.`
307+
const hintMessage = `
247308
${server.config.server.fs.allow.map((i) => `- ${i}`).join('\n')}
248309
249310
Refer to docs https://vite.dev/config/server-options.html#server-fs-allow for configurations and more details.`
250311

251-
server.config.logger.error(urlMessage)
252-
server.config.logger.warnOnce(hintMessage + '\n')
253-
res.statusCode = 403
254-
res.write(renderRestrictedErrorHTML(urlMessage + '\n' + hintMessage))
255-
res.end()
256-
} else {
257-
// if the file doesn't exist, we shouldn't restrict this path as it can
258-
// be an API call. Middlewares would issue a 404 if the file isn't handled
259-
next()
260-
}
261-
return false
312+
server.config.logger.error(urlMessage)
313+
server.config.logger.warnOnce(hintMessage + '\n')
314+
res.statusCode = 403
315+
res.write(renderRestrictedErrorHTML(urlMessage + '\n' + hintMessage))
316+
res.end()
262317
}
263318

264319
function renderRestrictedErrorHTML(msg: string): string {

‎packages/vite/src/node/server/middlewares/transform.ts

Copy file name to clipboardExpand all lines: packages/vite/src/node/server/middlewares/transform.ts
+19-8Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import { ERR_CLOSED_SERVER } from '../pluginContainer'
3939
import { getDepsOptimizer } from '../../optimizer'
4040
import { cleanUrl, unwrapId, withTrailingSlash } from '../../../shared/utils'
4141
import { NULL_BYTE_PLACEHOLDER } from '../../../shared/constants'
42-
import { ensureServingAccess } from './static'
42+
import { checkServingAccess, respondWithAccessDenied } from './static'
4343

4444
const debugCache = createDebugger('vite:cache')
4545

@@ -58,13 +58,24 @@ function deniedServingAccessForTransform(
5858
res: ServerResponse,
5959
next: Connect.NextFunction,
6060
) {
61-
return (
62-
(rawRE.test(url) ||
63-
urlRE.test(url) ||
64-
inlineRE.test(url) ||
65-
svgRE.test(url)) &&
66-
!ensureServingAccess(url, server, res, next)
67-
)
61+
if (
62+
rawRE.test(url) ||
63+
urlRE.test(url) ||
64+
inlineRE.test(url) ||
65+
svgRE.test(url)
66+
) {
67+
const servingAccessResult = checkServingAccess(url, server)
68+
if (servingAccessResult === 'denied') {
69+
respondWithAccessDenied(url, server, res)
70+
return true
71+
}
72+
if (servingAccessResult === 'fallback') {
73+
next()
74+
return true
75+
}
76+
servingAccessResult satisfies 'allowed'
77+
}
78+
return false
6879
}
6980

7081
/**

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

Copy file name to clipboardExpand all lines: playground/fs-serve/__tests__/fs-serve.spec.ts
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,4 +465,17 @@ describe.runIf(isServe)('invalid request', () => {
465465
)
466466
expect(response).toContain('HTTP/1.1 400 Bad Request')
467467
})
468+
469+
test('should deny request to denied file when a request has /.', async () => {
470+
const response = await sendRawRequest(viteTestUrl, '/src/dummy.crt/.')
471+
expect(response).toContain('HTTP/1.1 403 Forbidden')
472+
})
473+
474+
test('should deny request with /@fs/ to denied file when a request has /.', async () => {
475+
const response = await sendRawRequest(
476+
viteTestUrl,
477+
path.posix.join('/@fs/', root, 'root/src/dummy.crt/') + '.',
478+
)
479+
expect(response).toContain('HTTP/1.1 403 Forbidden')
480+
})
468481
})
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
secret

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.