From 95dbcf4dec273acc6c0efe97ac11c77375a2c2da Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sun, 13 Jun 2021 22:21:15 +0800 Subject: [PATCH 01/11] fix: fs-serve import graph awareness --- .../vite/src/node/plugins/importAnalysis.ts | 5 ++ packages/vite/src/node/server/index.ts | 15 +++- .../src/node/server/middlewares/static.ts | 76 +++++++++---------- packages/vite/src/node/server/moduleGraph.ts | 1 + .../vite/src/node/server/transformRequest.ts | 8 +- 5 files changed, 61 insertions(+), 44 deletions(-) diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index a1ad68c49104bd..7783882c6a30ff 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -361,6 +361,11 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { ) let url = normalizedUrl + // record as safe modules + server?.moduleGraph.safeModulesPath.add( + cleanUrl(url).slice(4 /* '/@fs'.length */) + ) + // rewrite if (url !== specifier) { // for optimized cjs deps, support named imports by rewriting named diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 0e06d6bc52808a..72021f7e1272b4 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -53,6 +53,7 @@ import { createMissingImporterRegisterFn } from '../optimizer/registerMissing' import { printServerUrls } from '../logger' import { resolveHostname } from '../utils' import { searchForWorkspaceRoot } from './searchRoot' +import { CLIENT_DIR } from '../constants' export interface ServerOptions { host?: string | boolean @@ -151,6 +152,8 @@ export interface FileSystemServeOptions { * @expiremental */ root?: string + + dirs?: string[] } /** @@ -484,7 +487,7 @@ export async function createServer( middlewares.use(transformMiddleware(server)) // serve static files - middlewares.use(serveRawFsMiddleware(config)) + middlewares.use(serveRawFsMiddleware(server, config)) middlewares.use(serveStaticMiddleware(root, config)) // spa fallback @@ -703,11 +706,19 @@ export function resolveServerOptions( const fsServeRoot = normalizePath( path.resolve(root, server.fsServe?.root || searchForWorkspaceRoot(root)) ) + const fsServeDirs = (server.fsServe?.dirs || []).map((i) => + normalizePath(path.resolve(root, i)) + ) + + fsServeDirs.push(CLIENT_DIR) + fsServeDirs.push(fsServeRoot) + // TODO: make strict by default const fsServeStrict = server.fsServe?.strict ?? false server.fsServe = { root: fsServeRoot, - strict: fsServeStrict + strict: fsServeStrict, + dirs: fsServeDirs } 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 5c01c9101a25b0..2c3be25be45e38 100644 --- a/packages/vite/src/node/server/middlewares/static.ts +++ b/packages/vite/src/node/server/middlewares/static.ts @@ -1,10 +1,8 @@ import path from 'path' import sirv, { Options } from 'sirv' import { Connect } from 'types/connect' -import { FileSystemServeOptions } from '..' -import { normalizePath, ResolvedConfig } from '../..' +import { normalizePath, ResolvedConfig, ViteDevServer } from '../..' import { FS_PREFIX } from '../../constants' -import { Logger } from '../../logger' import { cleanUrl, fsPathFromId, isImportRequest, isWindows } from '../../utils' import { AccessRestrictedError } from './error' @@ -77,6 +75,7 @@ export function serveStaticMiddleware( } export function serveRawFsMiddleware( + server: ViteDevServer, config: ResolvedConfig ): Connect.NextHandleFunction { const serveFromRoot = sirv('/', sirvOptions) @@ -90,11 +89,7 @@ export function serveRawFsMiddleware( // searching based from fs root. if (url.startsWith(FS_PREFIX)) { // restrict files outside of `fsServe.root` - ensureServingAccess( - path.resolve(fsPathFromId(url)), - config.server.fsServe, - config.logger - ) + ensureServingAccess(path.resolve(fsPathFromId(url)), server) url = url.slice(FS_PREFIX.length) if (isWindows) url = url.replace(/^[A-Z]:/i, '') @@ -107,40 +102,43 @@ export function serveRawFsMiddleware( } } -export function isFileAccessAllowed( +export function isFileServingAllowed( url: string, - { root, strict }: Required + server: ViteDevServer ): boolean { - return !strict || normalizePath(url).startsWith(root + path.posix.sep) + if (!server.config.server.fsServe.strict) return true + + const file = cleanUrl(url) + + if (server.moduleGraph.safeModulesPath.has(file)) { + return true + } + + const normalizedUrl = normalizePath(file) + return server.config.server.fsServe.dirs.some((i) => + normalizedUrl.startsWith(i + path.posix.sep) + ) } -export function ensureServingAccess( - url: string, - serveOptions: Required, - logger: Logger -): void { - const { strict, root } = serveOptions - // TODO: early return, should remove once we polished the restriction logic - if (!strict) return - - if (!isFileAccessAllowed(url, serveOptions)) { - const normalizedUrl = normalizePath(url) - if (strict) { - throw new AccessRestrictedError( - `The request url "${normalizedUrl}" is outside of vite dev server root "${root}". - For security concerns, accessing files outside of workspace root is restricted since Vite v2.3.x. - Refer to docs https://vitejs.dev/config/#server-fsserve-root for configurations and more details.`, - normalizedUrl, - root - ) - } else { - // TODO: warn for potential unrestricted access - logger.warnOnce( - `For security concerns, accessing files outside of workspace root will ` + - `be restricted by default in the future version of Vite. ` + - `Refer to [] for more` - ) - logger.warnOnce(`Unrestricted file system access to "${normalizedUrl}"`) - } +export function ensureServingAccess(url: string, server: ViteDevServer): void { + if (isFileServingAllowed(url, server)) return + + const { strict, root } = server.config.server.fsServe + + if (strict) { + throw new AccessRestrictedError( + `The request url "${url}" is outside of vite dev server root "${root}". + For security concerns, accessing files outside of workspace root is restricted since Vite v2.3.x. + Refer to docs https://vitejs.dev/config/#server-fsserve-root for configurations and more details.`, + url, + root + ) + } else { + server.config.logger.warnOnce(`Unrestricted file system access to "${url}"`) + server.config.logger.warnOnce( + `For security concerns, accessing files outside of workspace root will ` + + `be restricted by default in the future version of Vite. ` + + `Refer to [] for more` + ) } } diff --git a/packages/vite/src/node/server/moduleGraph.ts b/packages/vite/src/node/server/moduleGraph.ts index c5c94b749c9add..465fb4c93adeca 100644 --- a/packages/vite/src/node/server/moduleGraph.ts +++ b/packages/vite/src/node/server/moduleGraph.ts @@ -50,6 +50,7 @@ export class ModuleGraph { idToModuleMap = new Map() // a single file may corresponds to multiple modules with different queries fileToModulesMap = new Map>() + safeModulesPath = new Set() container: PluginContainer constructor(container: PluginContainer) { diff --git a/packages/vite/src/node/server/transformRequest.ts b/packages/vite/src/node/server/transformRequest.ts index 5e506bd36299bb..b04f844200599e 100644 --- a/packages/vite/src/node/server/transformRequest.ts +++ b/packages/vite/src/node/server/transformRequest.ts @@ -16,7 +16,7 @@ import { import { checkPublicFile } from '../plugins/asset' import { ssrTransform } from '../ssr/ssrTransform' import { injectSourcesContent } from './sourcemap' -import { isFileAccessAllowed } from './middlewares/static' +import { isFileServingAllowed } from './middlewares/static' const debugLoad = createDebugger('vite:load') const debugTransform = createDebugger('vite:transform') @@ -37,9 +37,11 @@ export interface TransformOptions { export async function transformRequest( url: string, - { config, pluginContainer, moduleGraph, watcher }: ViteDevServer, + server: ViteDevServer, options: TransformOptions = {} ): Promise { + const { config, pluginContainer, moduleGraph, watcher } = server + url = removeTimestampQuery(url) const { root, logger } = config const prettyUrl = isDebug ? prettifyUrl(url, root) : '' @@ -75,7 +77,7 @@ export async function transformRequest( // as string // only try the fallback if access is allowed, skip for out of root url // like /service-worker.js or /api/users - if (options.ssr || isFileAccessAllowed(file, config.server.fsServe)) { + if (options.ssr || isFileServingAllowed(file, server)) { try { code = await fs.readFile(file, 'utf-8') isDebug && debugLoad(`${timeFrom(loadStart)} [fs] ${prettyUrl}`) From fbe1829051a163d1905777360d5de81da253b8f5 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Sun, 13 Jun 2021 22:33:59 +0800 Subject: [PATCH 02/11] test: add test for fs-serve --- .../fs-serve/__tests__/fs-serve.spec.ts | 26 +++++++++++++ packages/playground/fs-serve/entry.js | 5 +++ packages/playground/fs-serve/nested/foo.js | 1 + packages/playground/fs-serve/package.json | 11 ++++++ packages/playground/fs-serve/root/index.html | 39 +++++++++++++++++++ .../playground/fs-serve/root/vite.config.js | 19 +++++++++ packages/playground/fs-serve/safe.json | 3 ++ packages/playground/fs-serve/unsafe.json | 3 ++ scripts/jestPerTestSetup.ts | 13 +++++-- 9 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 packages/playground/fs-serve/__tests__/fs-serve.spec.ts create mode 100644 packages/playground/fs-serve/entry.js create mode 100644 packages/playground/fs-serve/nested/foo.js create mode 100644 packages/playground/fs-serve/package.json create mode 100644 packages/playground/fs-serve/root/index.html create mode 100644 packages/playground/fs-serve/root/vite.config.js create mode 100644 packages/playground/fs-serve/safe.json create mode 100644 packages/playground/fs-serve/unsafe.json diff --git a/packages/playground/fs-serve/__tests__/fs-serve.spec.ts b/packages/playground/fs-serve/__tests__/fs-serve.spec.ts new file mode 100644 index 00000000000000..f9540468825b00 --- /dev/null +++ b/packages/playground/fs-serve/__tests__/fs-serve.spec.ts @@ -0,0 +1,26 @@ +import { isBuild } from '../../testUtils' + +const json = require('../safe.json') +const stringified = JSON.stringify(json) + +if (!isBuild) { + test('default import', async () => { + expect(await page.textContent('.full')).toBe(stringified) + }) + + test('named import', async () => { + expect(await page.textContent('.named')).toBe(json.msg) + }) + + test('safe fetch', async () => { + expect(await page.textContent('.safe-fetch')).toBe(stringified) + }) + + test('unsafe fetch', async () => { + expect(await page.textContent('.unsafe-fetch')).toBe('') + }) +} 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/entry.js b/packages/playground/fs-serve/entry.js new file mode 100644 index 00000000000000..b133236e632f16 --- /dev/null +++ b/packages/playground/fs-serve/entry.js @@ -0,0 +1,5 @@ +import { msg } from './nested/foo' + +export const fullmsg = msg + 'bar' + +document.querySelector('.nested-entry').textContent = fullmsg diff --git a/packages/playground/fs-serve/nested/foo.js b/packages/playground/fs-serve/nested/foo.js new file mode 100644 index 00000000000000..4eeb2ac0e1dbb4 --- /dev/null +++ b/packages/playground/fs-serve/nested/foo.js @@ -0,0 +1 @@ +export const msg = 'foo' diff --git a/packages/playground/fs-serve/package.json b/packages/playground/fs-serve/package.json new file mode 100644 index 00000000000000..ee1878c8ba6c4f --- /dev/null +++ b/packages/playground/fs-serve/package.json @@ -0,0 +1,11 @@ +{ + "name": "test-fs-serve", + "private": true, + "version": "0.0.0", + "scripts": { + "dev": "vite", + "build": "vite build", + "debug": "node --inspect-brk ../../vite/bin/vite", + "serve": "vite preview" + } +} diff --git a/packages/playground/fs-serve/root/index.html b/packages/playground/fs-serve/root/index.html new file mode 100644 index 00000000000000..a2c2915e48ed02 --- /dev/null +++ b/packages/playground/fs-serve/root/index.html @@ -0,0 +1,39 @@ +

Normal Import

+

+

+
+

Safe Fetch

+

+
+

Unsafe Fetch

+

+
+

Nested Entry

+

+
+
diff --git a/packages/playground/fs-serve/root/vite.config.js b/packages/playground/fs-serve/root/vite.config.js
new file mode 100644
index 00000000000000..70356a8d82b5fb
--- /dev/null
+++ b/packages/playground/fs-serve/root/vite.config.js
@@ -0,0 +1,19 @@
+const path = require('path')
+
+/**
+ * @type {import('vite').UserConfig}
+ */
+module.exports = {
+  server: {
+    fsServe: {
+      root: __dirname,
+      strict: true
+    },
+    hmr: {
+      overlay: false
+    }
+  },
+  define: {
+    ROOT: JSON.stringify(path.dirname(__dirname).replace(/\\/g, '/'))
+  }
+}
diff --git a/packages/playground/fs-serve/safe.json b/packages/playground/fs-serve/safe.json
new file mode 100644
index 00000000000000..84f96593c10bad
--- /dev/null
+++ b/packages/playground/fs-serve/safe.json
@@ -0,0 +1,3 @@
+{
+  "msg": "safe"
+}
diff --git a/packages/playground/fs-serve/unsafe.json b/packages/playground/fs-serve/unsafe.json
new file mode 100644
index 00000000000000..9b26fb7a8959fb
--- /dev/null
+++ b/packages/playground/fs-serve/unsafe.json
@@ -0,0 +1,3 @@
+{
+  "msg": "unsafe"
+}
diff --git a/scripts/jestPerTestSetup.ts b/scripts/jestPerTestSetup.ts
index f240ac430e056d..601cd02a1e2f55 100644
--- a/scripts/jestPerTestSetup.ts
+++ b/scripts/jestPerTestSetup.ts
@@ -24,6 +24,7 @@ declare global {
 
 let server: ViteDevServer | http.Server
 let tempDir: string
+let rootDir: string
 let err: Error
 
 const logs = ((global as any).browserLogs = [])
@@ -60,16 +61,20 @@ beforeAll(async () => {
         }
       })
 
+      // when `root` dir is present, use it as vite's root
+      let testCustomRoot = resolve(tempDir, 'root')
+      rootDir = fs.existsSync(testCustomRoot) ? testCustomRoot : tempDir
+
       const testCustomServe = resolve(dirname(testPath), 'serve.js')
       if (fs.existsSync(testCustomServe)) {
         // test has custom server configuration.
         const { serve } = require(testCustomServe)
-        server = await serve(tempDir, isBuildTest)
+        server = await serve(rootDir, isBuildTest)
         return
       }
 
       const options: UserConfig = {
-        root: tempDir,
+        root: rootDir,
         logLevel: 'silent',
         server: {
           watch: {
@@ -128,7 +133,7 @@ afterAll(async () => {
 
 function startStaticServer(): Promise {
   // check if the test project has base config
-  const configFile = resolve(tempDir, 'vite.config.js')
+  const configFile = resolve(rootDir, 'vite.config.js')
   let config: UserConfig
   try {
     config = require(configFile)
@@ -142,7 +147,7 @@ function startStaticServer(): Promise {
   }
 
   // start static file server
-  const serve = sirv(resolve(tempDir, 'dist'))
+  const serve = sirv(resolve(rootDir, 'dist'))
   const httpServer = (server = http.createServer((req, res) => {
     if (req.url === '/ping') {
       res.statusCode = 200

From 799ade5b1fcc9d07edcc085d660cc44d1d912e31 Mon Sep 17 00:00:00 2001
From: Anthony Fu 
Date: Mon, 14 Jun 2021 01:26:02 +0800
Subject: [PATCH 03/11] chore: try fix windows

---
 packages/playground/fs-serve/package.json           |  4 ++--
 packages/vite/src/node/server/middlewares/static.ts | 11 ++++++++---
 2 files changed, 10 insertions(+), 5 deletions(-)

diff --git a/packages/playground/fs-serve/package.json b/packages/playground/fs-serve/package.json
index ee1878c8ba6c4f..7f517900a229be 100644
--- a/packages/playground/fs-serve/package.json
+++ b/packages/playground/fs-serve/package.json
@@ -3,8 +3,8 @@
   "private": true,
   "version": "0.0.0",
   "scripts": {
-    "dev": "vite",
-    "build": "vite build",
+    "dev": "vite root",
+    "build": "vite build root",
     "debug": "node --inspect-brk ../../vite/bin/vite",
     "serve": "vite preview"
   }
diff --git a/packages/vite/src/node/server/middlewares/static.ts b/packages/vite/src/node/server/middlewares/static.ts
index 2c3be25be45e38..a15721b50e7e8c 100644
--- a/packages/vite/src/node/server/middlewares/static.ts
+++ b/packages/vite/src/node/server/middlewares/static.ts
@@ -3,7 +3,13 @@ import sirv, { Options } from 'sirv'
 import { Connect } from 'types/connect'
 import { normalizePath, ResolvedConfig, ViteDevServer } from '../..'
 import { FS_PREFIX } from '../../constants'
-import { cleanUrl, fsPathFromId, isImportRequest, isWindows } from '../../utils'
+import {
+  cleanUrl,
+  fsPathFromId,
+  isImportRequest,
+  isWindows,
+  slash
+} from '../../utils'
 import { AccessRestrictedError } from './error'
 
 const sirvOptions: Options = {
@@ -89,8 +95,7 @@ export function serveRawFsMiddleware(
     // searching based from fs root.
     if (url.startsWith(FS_PREFIX)) {
       // restrict files outside of `fsServe.root`
-      ensureServingAccess(path.resolve(fsPathFromId(url)), server)
-
+      ensureServingAccess(slash(path.resolve(fsPathFromId(url))), server)
       url = url.slice(FS_PREFIX.length)
       if (isWindows) url = url.replace(/^[A-Z]:/i, '')
 

From a8ed6b4e59d25499b0f374972fc0d3ae162a36f1 Mon Sep 17 00:00:00 2001
From: Anthony Fu 
Date: Mon, 14 Jun 2021 02:27:00 +0800
Subject: [PATCH 04/11] fix: windows

---
 packages/playground/fs-serve/root/index.html        |  4 ++--
 packages/vite/src/node/plugins/importAnalysis.ts    |  3 ++-
 packages/vite/src/node/server/index.ts              | 10 ++++++----
 packages/vite/src/node/server/middlewares/static.ts | 12 +++++-------
 packages/vite/src/node/utils.ts                     |  4 ++++
 5 files changed, 19 insertions(+), 14 deletions(-)

diff --git a/packages/playground/fs-serve/root/index.html b/packages/playground/fs-serve/root/index.html
index a2c2915e48ed02..edd6b27c22a3a2 100644
--- a/packages/playground/fs-serve/root/index.html
+++ b/packages/playground/fs-serve/root/index.html
@@ -19,14 +19,14 @@ 

Nested Entry

text('.named', msg) // imported before, should be treated as safe - fetch('/@fs' + ROOT + '/safe.json') + fetch('/@fs/' + ROOT + '/safe.json') .then((r) => r.json()) .then((data) => { text('.safe-fetch', JSON.stringify(data)) }) // not imported before, outside of root, treated as unsafe - fetch('/@fs' + ROOT + '/unsafe.json') + fetch('/@fs/' + ROOT + '/unsafe.json') .catch((e) => console.error(e)) .then((r) => r.json()) .then((data) => { diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index 7783882c6a30ff..5c32d6bb969e1f 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -17,7 +17,8 @@ import { isJSRequest, prettifyUrl, timeFrom, - normalizePath + normalizePath, + fsPathFromId } from '../utils' import { debugHmr, diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 72021f7e1272b4..3a927d321ddc15 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -33,7 +33,7 @@ import { import { timeMiddleware } from './middlewares/time' import { ModuleGraph, ModuleNode } from './moduleGraph' import { Connect } from 'types/connect' -import { createDebugger, normalizePath } from '../utils' +import { createDebugger, ensureLeadingSlash, normalizePath } from '../utils' import { errorMiddleware, prepareError } from './middlewares/error' import { handleHMRUpdate, HmrOptions, handleFileAddUnlink } from './hmr' import { openBrowser } from './openBrowser' @@ -487,7 +487,7 @@ export async function createServer( middlewares.use(transformMiddleware(server)) // serve static files - middlewares.use(serveRawFsMiddleware(server, config)) + middlewares.use(serveRawFsMiddleware(server)) middlewares.use(serveStaticMiddleware(root, config)) // spa fallback @@ -706,13 +706,15 @@ export function resolveServerOptions( const fsServeRoot = normalizePath( path.resolve(root, server.fsServe?.root || searchForWorkspaceRoot(root)) ) - const fsServeDirs = (server.fsServe?.dirs || []).map((i) => + let fsServeDirs = (server.fsServe?.dirs || []).map((i) => normalizePath(path.resolve(root, i)) ) - fsServeDirs.push(CLIENT_DIR) + fsServeDirs.push(normalizePath(CLIENT_DIR)) fsServeDirs.push(fsServeRoot) + fsServeDirs = fsServeDirs.map(ensureLeadingSlash) + // TODO: make strict by default const fsServeStrict = server.fsServe?.strict ?? false server.fsServe = { diff --git a/packages/vite/src/node/server/middlewares/static.ts b/packages/vite/src/node/server/middlewares/static.ts index a15721b50e7e8c..4b90ef47f84cc3 100644 --- a/packages/vite/src/node/server/middlewares/static.ts +++ b/packages/vite/src/node/server/middlewares/static.ts @@ -1,3 +1,4 @@ +import { fileSync } from 'brotli-size' import path from 'path' import sirv, { Options } from 'sirv' import { Connect } from 'types/connect' @@ -5,6 +6,7 @@ import { normalizePath, ResolvedConfig, ViteDevServer } from '../..' import { FS_PREFIX } from '../../constants' import { cleanUrl, + ensureLeadingSlash, fsPathFromId, isImportRequest, isWindows, @@ -81,8 +83,7 @@ export function serveStaticMiddleware( } export function serveRawFsMiddleware( - server: ViteDevServer, - config: ResolvedConfig + server: ViteDevServer ): Connect.NextHandleFunction { const serveFromRoot = sirv('/', sirvOptions) @@ -113,16 +114,13 @@ export function isFileServingAllowed( ): boolean { if (!server.config.server.fsServe.strict) return true - const file = cleanUrl(url) + const file = ensureLeadingSlash(normalizePath(cleanUrl(url))) if (server.moduleGraph.safeModulesPath.has(file)) { return true } - const normalizedUrl = normalizePath(file) - return server.config.server.fsServe.dirs.some((i) => - normalizedUrl.startsWith(i + path.posix.sep) - ) + return server.config.server.fsServe.dirs.some((i) => file.startsWith(i + '/')) } export function ensureServingAccess(url: string, server: ViteDevServer): void { diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 15c86b715a81d1..363c9c630052c9 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -361,6 +361,10 @@ export function copyDir(srcDir: string, destDir: string): void { } } +export function ensureLeadingSlash(path: string) { + return !path.startsWith('/') ? '/' + path : path +} + export function ensureWatchedFile( watcher: FSWatcher, file: string | null, From 699ef7d50ff4e594ce721f300198c99a8a9a54ce Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Mon, 14 Jun 2021 02:35:21 +0800 Subject: [PATCH 05/11] chore: update --- packages/vite/src/node/plugins/importAnalysis.ts | 3 +-- packages/vite/src/node/server/middlewares/static.ts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index 5c32d6bb969e1f..7783882c6a30ff 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -17,8 +17,7 @@ import { isJSRequest, prettifyUrl, timeFrom, - normalizePath, - fsPathFromId + normalizePath } from '../utils' import { debugHmr, diff --git a/packages/vite/src/node/server/middlewares/static.ts b/packages/vite/src/node/server/middlewares/static.ts index 4b90ef47f84cc3..e8048d815fd360 100644 --- a/packages/vite/src/node/server/middlewares/static.ts +++ b/packages/vite/src/node/server/middlewares/static.ts @@ -1,4 +1,3 @@ -import { fileSync } from 'brotli-size' import path from 'path' import sirv, { Options } from 'sirv' import { Connect } from 'types/connect' From c5e2c6f15e5640ed070edb41e547472312588a45 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Mon, 14 Jun 2021 03:26:44 +0800 Subject: [PATCH 06/11] Update packages/vite/src/node/utils.ts Co-authored-by: Shinigami --- packages/vite/src/node/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 363c9c630052c9..18ff1f1d51b88d 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -361,7 +361,7 @@ export function copyDir(srcDir: string, destDir: string): void { } } -export function ensureLeadingSlash(path: string) { +export function ensureLeadingSlash(path: string): string { return !path.startsWith('/') ? '/' + path : path } From eb5f12a173617347cab87e39b8435a6603d2c751 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Mon, 14 Jun 2021 03:32:53 +0800 Subject: [PATCH 07/11] chore: add missing test --- packages/playground/fs-serve/__tests__/fs-serve.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/playground/fs-serve/__tests__/fs-serve.spec.ts b/packages/playground/fs-serve/__tests__/fs-serve.spec.ts index f9540468825b00..d8dc4d357f368f 100644 --- a/packages/playground/fs-serve/__tests__/fs-serve.spec.ts +++ b/packages/playground/fs-serve/__tests__/fs-serve.spec.ts @@ -19,6 +19,10 @@ if (!isBuild) { test('unsafe fetch', async () => { expect(await page.textContent('.unsafe-fetch')).toBe('') }) + + test('nested entry', async () => { + expect(await page.textContent('.nested-entry')).toBe('foobar') + }) } else { test('dummy test to make jest happy', async () => { // Your test suite must contain at least one test. From 7162852c6b0467f70115e7e57ca8c8a783e28c8d Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Tue, 15 Jun 2021 10:47:08 +0800 Subject: [PATCH 08/11] chore: update warning timing --- packages/vite/src/node/server/index.ts | 11 ++++--- .../src/node/server/middlewares/static.ts | 32 ++++++++++--------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 3a927d321ddc15..4ef9b453b65959 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -137,11 +137,13 @@ export interface FileSystemServeOptions { /** * Strictly restrict file accessing outside of allowing paths. * + * Set to `false` to disable the warning * Default to false at this moment, will enabled by default in the future versions. + * * @expiremental - * @default false + * @default undefined */ - strict?: boolean + strict?: boolean | undefined /** * Restrict accessing files outside this directory will result in a 403. @@ -715,11 +717,10 @@ export function resolveServerOptions( fsServeDirs = fsServeDirs.map(ensureLeadingSlash) - // TODO: make strict by default - const fsServeStrict = server.fsServe?.strict ?? false server.fsServe = { root: fsServeRoot, - strict: fsServeStrict, + // TODO: make strict by default + strict: server.fsServe?.strict, dirs: fsServeDirs } 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 e8048d815fd360..3565b91326f2a4 100644 --- a/packages/vite/src/node/server/middlewares/static.ts +++ b/packages/vite/src/node/server/middlewares/static.ts @@ -111,23 +111,32 @@ export function isFileServingAllowed( url: string, server: ViteDevServer ): boolean { - if (!server.config.server.fsServe.strict) return true + // explicitly disabled + if (server.config.server.fsServe.strict === false) return true const file = ensureLeadingSlash(normalizePath(cleanUrl(url))) - if (server.moduleGraph.safeModulesPath.has(file)) { + if (server.moduleGraph.safeModulesPath.has(file)) return true + + if (server.config.server.fsServe.dirs.some((i) => file.startsWith(i + '/'))) + return true + + if (!server.config.server.fsServe.strict) { + server.config.logger.warnOnce(`Unrestricted file system access to "${url}"`) + server.config.logger.warnOnce( + `For security concerns, accessing files outside of workspace root will ` + + `be restricted by default in the future version of Vite. ` + + `Refer to [] for more` + ) return true } - return server.config.server.fsServe.dirs.some((i) => file.startsWith(i + '/')) + return false } export function ensureServingAccess(url: string, server: ViteDevServer): void { - if (isFileServingAllowed(url, server)) return - - const { strict, root } = server.config.server.fsServe - - if (strict) { + if (!isFileServingAllowed(url, server)) { + const root = server.config.server.fsServe.root throw new AccessRestrictedError( `The request url "${url}" is outside of vite dev server root "${root}". For security concerns, accessing files outside of workspace root is restricted since Vite v2.3.x. @@ -135,12 +144,5 @@ export function ensureServingAccess(url: string, server: ViteDevServer): void { url, root ) - } else { - server.config.logger.warnOnce(`Unrestricted file system access to "${url}"`) - server.config.logger.warnOnce( - `For security concerns, accessing files outside of workspace root will ` + - `be restricted by default in the future version of Vite. ` + - `Refer to [] for more` - ) } } From 66649e9f320e4d9165ec10bf2b80b3918876be19 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Tue, 15 Jun 2021 10:52:59 +0800 Subject: [PATCH 09/11] chore: add more test --- .../fs-serve/__tests__/fs-serve.spec.ts | 2 ++ packages/playground/fs-serve/root/index.html | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/playground/fs-serve/__tests__/fs-serve.spec.ts b/packages/playground/fs-serve/__tests__/fs-serve.spec.ts index d8dc4d357f368f..af09045be48b1a 100644 --- a/packages/playground/fs-serve/__tests__/fs-serve.spec.ts +++ b/packages/playground/fs-serve/__tests__/fs-serve.spec.ts @@ -14,10 +14,12 @@ if (!isBuild) { test('safe fetch', async () => { expect(await page.textContent('.safe-fetch')).toBe(stringified) + expect(await page.textContent('.safe-fetch-status')).toBe('200') }) test('unsafe fetch', async () => { expect(await page.textContent('.unsafe-fetch')).toBe('') + expect(await page.textContent('.unsafe-fetch-status')).toBe('403') }) test('nested entry', async () => { diff --git a/packages/playground/fs-serve/root/index.html b/packages/playground/fs-serve/root/index.html index edd6b27c22a3a2..1f100557ba3e5b 100644 --- a/packages/playground/fs-serve/root/index.html +++ b/packages/playground/fs-serve/root/index.html @@ -3,9 +3,11 @@

Normal Import


 
 

Safe Fetch

+

 

 
 

Unsafe Fetch

+

 

 
 

Nested Entry

@@ -20,18 +22,26 @@

Nested Entry

// imported before, should be treated as safe fetch('/@fs/' + ROOT + '/safe.json') - .then((r) => r.json()) + .then((r) => { + text('.safe-fetch-status', r.status) + return r.json() + }) .then((data) => { text('.safe-fetch', JSON.stringify(data)) }) // not imported before, outside of root, treated as unsafe fetch('/@fs/' + ROOT + '/unsafe.json') - .catch((e) => console.error(e)) - .then((r) => r.json()) + .then((r) => { + text('.unsafe-fetch-status', r.status) + return r.json() + }) .then((data) => { text('.unsafe-fetch', JSON.stringify(data)) }) + .catch((e) => { + console.error(e) + }) function text(sel, text) { document.querySelector(sel).textContent = text From 41aa20666dab22539bb815ecdacc6a20bc687242 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Tue, 15 Jun 2021 11:10:55 +0800 Subject: [PATCH 10/11] chore: deprecated `fsServe.root` and in favor of `fsServe.allow` --- docs/config/index.md | 10 +++--- packages/vite/src/node/server/index.ts | 35 ++++++++++--------- .../vite/src/node/server/middlewares/error.ts | 2 +- .../src/node/server/middlewares/static.ts | 18 +++++----- 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/docs/config/index.md b/docs/config/index.md index 6c6370fb0c8dbf..be11392ea0f349 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -488,12 +488,12 @@ createServer() Restrict serving files outside of workspace root. -### server.fsServe.root +### server.fsServe.allow - **Experimental** -- **Type:** `string` +- **Type:** `string[]` - Restrict files that could be served via `/@fs/`. When `server.fsServe.strict` is set to `true`, accessing files outside this directory will result in a 403. + Restrict files that could be served via `/@fs/`. When `server.fsServe.strict` is set to `true`, accessing files outside this directory list will result in a 403. Vite will search for the root of the potential workspace and use it as default. A valid workspace met the following conditions, otherwise will fallback to the [project root](/guide/#index-html-and-project-root). @@ -508,7 +508,9 @@ createServer() server: { fsServe: { // Allow serving files from one level up to the project root - root: '..' + allow: [ + '..' + ] } } } diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 4ef9b453b65959..1c1e3fdd5e3406 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -130,7 +130,7 @@ export interface ServerOptions { } export interface ResolvedServerOptions extends ServerOptions { - fsServe: Required + fsServe: Required> } export interface FileSystemServeOptions { @@ -146,16 +146,21 @@ export interface FileSystemServeOptions { strict?: boolean | undefined /** - * Restrict accessing files outside this directory will result in a 403. + * + * @expiremental + * @deprecated use `fsServe.allow` instead + */ + root?: string + + /** + * Restrict accessing files outside the allowed directories. * * Accepts absolute path or a path relative to project root. * Will try to search up for workspace root by default. * * @expiremental */ - root?: string - - dirs?: string[] + allow?: string[] } /** @@ -705,23 +710,21 @@ export function resolveServerOptions( raw?: ServerOptions ): ResolvedServerOptions { const server = raw || {} - const fsServeRoot = normalizePath( - path.resolve(root, server.fsServe?.root || searchForWorkspaceRoot(root)) - ) - let fsServeDirs = (server.fsServe?.dirs || []).map((i) => - normalizePath(path.resolve(root, i)) - ) + let allowDirs = server.fsServe?.allow - fsServeDirs.push(normalizePath(CLIENT_DIR)) - fsServeDirs.push(fsServeRoot) + if (!allowDirs) { + allowDirs = [server.fsServe?.root || searchForWorkspaceRoot(root)] + } + allowDirs.push(CLIENT_DIR) - fsServeDirs = fsServeDirs.map(ensureLeadingSlash) + allowDirs = allowDirs.map((i) => + ensureLeadingSlash(normalizePath(path.resolve(root, i))) + ) server.fsServe = { - root: fsServeRoot, // TODO: make strict by default strict: server.fsServe?.strict, - dirs: fsServeDirs + allow: allowDirs } return server as ResolvedServerOptions } diff --git a/packages/vite/src/node/server/middlewares/error.ts b/packages/vite/src/node/server/middlewares/error.ts index 3bc6eca98b2f71..5bb4e8ae7253bb 100644 --- a/packages/vite/src/node/server/middlewares/error.ts +++ b/packages/vite/src/node/server/middlewares/error.ts @@ -75,7 +75,7 @@ export function errorMiddleware( } export class AccessRestrictedError extends Error { - constructor(msg: string, public url: string, public serveRoot: string) { + constructor(msg: string) { super(msg) } } diff --git a/packages/vite/src/node/server/middlewares/static.ts b/packages/vite/src/node/server/middlewares/static.ts index 3565b91326f2a4..f386edc581a15b 100644 --- a/packages/vite/src/node/server/middlewares/static.ts +++ b/packages/vite/src/node/server/middlewares/static.ts @@ -118,15 +118,15 @@ export function isFileServingAllowed( if (server.moduleGraph.safeModulesPath.has(file)) return true - if (server.config.server.fsServe.dirs.some((i) => file.startsWith(i + '/'))) + if (server.config.server.fsServe.allow.some((i) => file.startsWith(i + '/'))) return true if (!server.config.server.fsServe.strict) { server.config.logger.warnOnce(`Unrestricted file system access to "${url}"`) server.config.logger.warnOnce( - `For security concerns, accessing files outside of workspace root will ` + + `For security concerns, accessing files outside of serving allow list will ` + `be restricted by default in the future version of Vite. ` + - `Refer to [] for more` + `Refer to https://vitejs.dev/config/#server-fsserve-allow for more details.` ) return true } @@ -136,13 +136,13 @@ export function isFileServingAllowed( export function ensureServingAccess(url: string, server: ViteDevServer): void { if (!isFileServingAllowed(url, server)) { - const root = server.config.server.fsServe.root + const allow = server.config.server.fsServe.allow throw new AccessRestrictedError( - `The request url "${url}" is outside of vite dev server root "${root}". - For security concerns, accessing files outside of workspace root is restricted since Vite v2.3.x. - Refer to docs https://vitejs.dev/config/#server-fsserve-root for configurations and more details.`, - url, - root + `The request url "${url}" is outside of Vite serving allow list: + +${allow.map((i) => `- ${i}`).join('\n')} + +Refer to docs https://vitejs.dev/config/#server-fsserve-allow for configurations and more details.` ) } } From 780348ed880ef5c899326a687ce9fd06b61c4f5e Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Tue, 15 Jun 2021 15:03:37 +0800 Subject: [PATCH 11/11] chore: update with @patak-js 's suggestions --- packages/vite/src/node/server/index.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/vite/src/node/server/index.ts b/packages/vite/src/node/server/index.ts index 1c1e3fdd5e3406..b8fe8d2e6632a6 100644 --- a/packages/vite/src/node/server/index.ts +++ b/packages/vite/src/node/server/index.ts @@ -705,6 +705,10 @@ function createServerCloseFn(server: http.Server | null) { }) } +function resolvedAllowDir(root: string, dir: string): string { + return ensureLeadingSlash(normalizePath(path.resolve(root, dir))) +} + export function resolveServerOptions( root: string, raw?: ServerOptions @@ -715,11 +719,13 @@ export function resolveServerOptions( if (!allowDirs) { allowDirs = [server.fsServe?.root || searchForWorkspaceRoot(root)] } - allowDirs.push(CLIENT_DIR) + allowDirs = allowDirs.map((i) => resolvedAllowDir(root, i)) - allowDirs = allowDirs.map((i) => - ensureLeadingSlash(normalizePath(path.resolve(root, i))) - ) + // only push client dir when vite itself is outside-of-root + const resolvedClientDir = resolvedAllowDir(root, CLIENT_DIR) + if (!allowDirs.some((i) => resolvedClientDir.startsWith(i))) { + allowDirs.push(resolvedClientDir) + } server.fsServe = { // TODO: make strict by default