Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: vitejs/vite
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v6.2.4
Choose a base ref
...
head repository: vitejs/vite
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v6.2.5
Choose a head ref
  • 2 commits
  • 9 files changed
  • 2 contributors

Commits on Apr 3, 2025

  1. fix: backport #19782, fs check with svg and relative paths

    patak-dev authored Apr 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    joamag João Magalhães
    Copy the full SHA
    fdb196e View commit details
  2. release: v6.2.5

    patak-dev committed Apr 3, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    joamag João Magalhães
    Copy the full SHA
    c176acf View commit details
6 changes: 6 additions & 0 deletions packages/vite/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## <small>6.2.5 (2025-04-03)</small>

* fix: backport #19782, fs check with svg and relative paths ([fdb196e](https://github.com/vitejs/vite/commit/fdb196e9f8672dba32cf5156c81665c7e82ac581)), closes [#19782](https://github.com/vitejs/vite/issues/19782)



## <small>6.2.4 (2025-03-31)</small>

* fix: fs check in transform middleware (#19761) ([7a4faba](https://github.com/vitejs/vite/commit/7a4fabab6a3aa24c89144e15a13d78f92b52e588)), closes [#19761](https://github.com/vitejs/vite/issues/19761)
2 changes: 1 addition & 1 deletion packages/vite/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vite",
"version": "6.2.4",
"version": "6.2.5",
"type": "module",
"license": "MIT",
"author": "Evan You",
5 changes: 3 additions & 2 deletions packages/vite/src/node/plugins/asset.ts
Original file line number Diff line number Diff line change
@@ -282,8 +282,9 @@ export async function fileToDevUrl(

// If is svg and it's inlined in build, also inline it in dev to match
// the behaviour in build due to quote handling differences.
if (svgExtRE.test(id)) {
const file = publicFile || cleanUrl(id)
const cleanedId = cleanUrl(id)
if (svgExtRE.test(cleanedId)) {
const file = publicFile || cleanedId
const content = await fsp.readFile(file)
if (shouldInline(environment, file, id, content, undefined, undefined)) {
return assetToDataURL(environment, file, content)
4 changes: 3 additions & 1 deletion packages/vite/src/node/plugins/wasm.ts
Original file line number Diff line number Diff line change
@@ -3,6 +3,8 @@ import { fileToUrl } from './asset'

const wasmHelperId = '\0vite/wasm-helper.js'

const wasmInitRE = /(?<![?#].*)\.wasm\?init/

const wasmHelper = async (opts = {}, url: string) => {
let result
if (url.startsWith('data:')) {
@@ -61,7 +63,7 @@ export const wasmHelperPlugin = (): Plugin => {
return `export default ${wasmHelperCode}`
}

if (!id.endsWith('.wasm?init')) {
if (!wasmInitRE.test(id)) {
return
}

35 changes: 30 additions & 5 deletions packages/vite/src/node/server/middlewares/transform.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from 'node:path'
import fsp from 'node:fs/promises'
import type { ServerResponse } from 'node:http'
import type { Connect } from 'dep-types/connect'
import colors from 'picocolors'
import type { ExistingRawSourceMap } from 'rollup'
@@ -16,7 +17,11 @@ import {
removeTimestampQuery,
} from '../../utils'
import { send } from '../send'
import { ERR_LOAD_URL, transformRequest } from '../transformRequest'
import {
ERR_DENIED_ID,
ERR_LOAD_URL,
transformRequest,
} from '../transformRequest'
import { applySourcemapIgnoreList } from '../sourcemap'
import { isHTMLProxy } from '../../plugins/html'
import {
@@ -47,6 +52,22 @@ const trailingQuerySeparatorsRE = /[?&]+$/
const urlRE = /[?&]url\b/
const rawRE = /[?&]raw\b/
const inlineRE = /[?&]inline\b/
const svgRE = /\.svg\b/

function deniedServingAccessForTransform(
url: string,
server: ViteDevServer,
res: ServerResponse,
next: Connect.NextFunction,
) {
return (
(rawRE.test(url) ||
urlRE.test(url) ||
inlineRE.test(url) ||
svgRE.test(url)) &&
!ensureServingAccess(url, server, res, next)
)
}

/**
* A middleware that short-circuits the middleware chain to serve cached transformed modules
@@ -178,10 +199,7 @@ export function transformMiddleware(
'',
)
if (
(rawRE.test(urlWithoutTrailingQuerySeparators) ||
urlRE.test(urlWithoutTrailingQuerySeparators) ||
inlineRE.test(urlWithoutTrailingQuerySeparators)) &&
!ensureServingAccess(
deniedServingAccessForTransform(
urlWithoutTrailingQuerySeparators,
server,
res,
@@ -231,6 +249,9 @@ export function transformMiddleware(
// resolve, load and transform using the plugin container
const result = await transformRequest(environment, url, {
html: req.headers.accept?.includes('text/html'),
allowId(id) {
return !deniedServingAccessForTransform(id, server, res, next)
},
})
if (result) {
const depsOptimizer = environment.depsOptimizer
@@ -301,6 +322,10 @@ export function transformMiddleware(
// Let other middleware handle if we can't load the url via transformRequest
return next()
}
if (e?.code === ERR_DENIED_ID) {
// next() is called in ensureServingAccess
return
}
return next(e)
}

11 changes: 11 additions & 0 deletions packages/vite/src/node/server/transformRequest.ts
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ import type { DevEnvironment } from './environment'

export const ERR_LOAD_URL = 'ERR_LOAD_URL'
export const ERR_LOAD_PUBLIC_URL = 'ERR_LOAD_PUBLIC_URL'
export const ERR_DENIED_ID = 'ERR_DENIED_ID'

const debugLoad = createDebugger('vite:load')
const debugTransform = createDebugger('vite:transform')
@@ -55,6 +56,10 @@ export interface TransformOptions {
* @internal
*/
html?: boolean
/**
* @internal
*/
allowId?: (id: string) => boolean
}

// TODO: This function could be moved to the DevEnvironment class.
@@ -248,6 +253,12 @@ async function loadAndTransform(

const moduleGraph = environment.moduleGraph

if (options.allowId && !options.allowId(id)) {
const err: any = new Error(`Denied ID ${id}`)
err.code = ERR_DENIED_ID
throw err
}

let code: string | null = null
let map: SourceDescription['map'] = null

24 changes: 24 additions & 0 deletions playground/fs-serve/__tests__/fs-serve.spec.ts
Original file line number Diff line number Diff line change
@@ -79,6 +79,16 @@ describe.runIf(isServe)('main', () => {
).toBe('403')
})

test('unsafe fetch ?.svg?import', async () => {
expect(
await page.textContent('.unsafe-fetch-query-dot-svg-import-status'),
).toBe('403')
})

test('unsafe fetch .svg?import', async () => {
expect(await page.textContent('.unsafe-fetch-svg-status')).toBe('403')
})

test('safe fs fetch', async () => {
expect(await page.textContent('.safe-fs-fetch')).toBe(stringified)
expect(await page.textContent('.safe-fs-fetch-status')).toBe('200')
@@ -144,6 +154,14 @@ describe.runIf(isServe)('main', () => {
).toBe('403')
})

test('unsafe fs fetch with relative path after query status', async () => {
expect(
await page.textContent(
'.unsafe-fs-fetch-relative-path-after-query-status',
),
).toBe('403')
})

test('nested entry', async () => {
expect(await page.textContent('.nested-entry')).toBe('foobar')
})
@@ -157,6 +175,12 @@ describe.runIf(isServe)('main', () => {
const code = await page.textContent('.unsafe-dotEnV-casing')
expect(code === '403' || code === '404').toBeTruthy()
})

test('denied env with ?.svg?.wasm?init', async () => {
expect(
await page.textContent('.unsafe-dotenv-query-dot-svg-wasm-init'),
).toBe('403')
})
})

describe('fetch', () => {
51 changes: 51 additions & 0 deletions playground/fs-serve/root/src/index.html
Original file line number Diff line number Diff line change
@@ -25,6 +25,8 @@ <h2>Unsafe Fetch</h2>
<pre class="unsafe-fetch-8498-2"></pre>
<pre class="unsafe-fetch-import-inline-status"></pre>
<pre class="unsafe-fetch-raw-query-import-status"></pre>
<pre class="unsafe-fetch-query-dot-svg-import-status"></pre>
<pre class="unsafe-fetch-svg-status"></pre>

<h2>Safe /@fs/ Fetch</h2>
<pre class="safe-fs-fetch-status"></pre>
@@ -49,13 +51,15 @@ <h2>Unsafe /@fs/ Fetch</h2>
<pre class="unsafe-fs-fetch-8498-2"></pre>
<pre class="unsafe-fs-fetch-import-inline-status"></pre>
<pre class="unsafe-fs-fetch-import-inline-wasm-init-status"></pre>
<pre class="unsafe-fs-fetch-relative-path-after-query-status"></pre>

<h2>Nested Entry</h2>
<pre class="nested-entry"></pre>

<h2>Denied</h2>
<pre class="unsafe-dotenv"></pre>
<pre class="unsafe-dotEnV-casing"></pre>
<pre class="unsafe-dotenv-query-dot-svg-wasm-init"></pre>

<script type="module">
import '../../entry'
@@ -182,6 +186,24 @@ <h2>Denied</h2>
console.error(e)
})

// outside of allowed dir with .svg query import
fetch(joinUrlSegments(base, '/unsafe.txt?.svg?import'))
.then((r) => {
text('.unsafe-fetch-query-dot-svg-import-status', r.status)
})
.catch((e) => {
console.error(e)
})

// svg outside of allowed dir, treated as unsafe
fetch(joinUrlSegments(base, '/unsafe.svg?import'))
.then((r) => {
text('.unsafe-fetch-svg-status', r.status)
})
.catch((e) => {
console.error(e)
})

// imported before, should be treated as safe
fetch(joinUrlSegments(base, joinUrlSegments('/@fs/', ROOT) + '/safe.json'))
.then((r) => {
@@ -298,6 +320,21 @@ <h2>Denied</h2>
console.error(e)
})

// outside of root with relative path after query
fetch(
joinUrlSegments(
base,
joinUrlSegments('/@fs/', ROOT) +
'/root/src/?/../../unsafe.txt?import&raw',
),
)
.then((r) => {
text('.unsafe-fs-fetch-relative-path-after-query-status', r.status)
})
.catch((e) => {
console.error(e)
})

// outside root with special characters #8498
fetch(
joinUrlSegments(
@@ -368,6 +405,20 @@ <h2>Denied</h2>
console.error(e)
})

// .env with .svg?.wasm?init
fetch(
joinUrlSegments(
base,
joinUrlSegments('/@fs/', ROOT) + '/root/src/.env?.svg?.wasm?init',
),
)
.then((r) => {
text('.unsafe-dotenv-query-dot-svg-wasm-init', r.status)
})
.catch((e) => {
console.error(e)
})

function text(sel, text) {
document.querySelector(sel).textContent = text
}
3 changes: 3 additions & 0 deletions playground/fs-serve/root/unsafe.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.