VYPR
Moderate severityNVD Advisory· Published Mar 24, 2025· Updated Mar 24, 2025

Vite bypasses server.fs.deny when using `?raw??`

CVE-2025-30208

Description

Vite, a provider of frontend development tooling, has a vulnerability in versions prior to 6.2.3, 6.1.2, 6.0.12, 5.4.15, and 4.5.10. @fs denies access to files outside of Vite serving allow list. Adding ?raw?? or ?import&raw?? to the URL bypasses this limitation and returns the file content if it exists. This bypass exists because trailing separators such as ? are removed in several places, but are not accounted for in query string regexes. The contents of arbitrary files can be returned to the browser. Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) are affected. Versions 6.2.3, 6.1.2, 6.0.12, 5.4.15, and 4.5.10 fix the issue.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
vitenpm
>= 6.2.0, < 6.2.36.2.3
vitenpm
>= 6.1.0, < 6.1.26.1.2
vitenpm
>= 6.0.0, < 6.0.126.0.12
vitenpm
>= 5.0.0, < 5.4.155.4.15
vitenpm
< 4.5.104.5.10

Affected products

1

Patches

5
315695e9d97c

fix: backport #19702, fs raw query with query separators (#19704)

https://github.com/vitejs/vite翠 / greenMar 24, 2025via ghsa
3 files changed · +65 2
  • packages/vite/src/node/server/middlewares/transform.ts+13 2 modified
    @@ -44,6 +44,7 @@ import { ensureServingAccess } from './static'
     const debugCache = createDebugger('vite:cache')
     
     const knownIgnoreList = new Set(['/', '/favicon.ico'])
    +const trailingQuerySeparatorsRE = /[?&]+$/
     
     export function transformMiddleware(
       server: ViteDevServer,
    @@ -167,9 +168,19 @@ export function transformMiddleware(
             }
           }
     
    +      const urlWithoutTrailingQuerySeparators = url.replace(
    +        trailingQuerySeparatorsRE,
    +        '',
    +      )
           if (
    -        (rawRE.test(url) || urlRE.test(url)) &&
    -        !ensureServingAccess(url, server, res, next)
    +        (rawRE.test(urlWithoutTrailingQuerySeparators) ||
    +          urlRE.test(urlWithoutTrailingQuerySeparators)) &&
    +        !ensureServingAccess(
    +          urlWithoutTrailingQuerySeparators,
    +          server,
    +          res,
    +          next,
    +        )
           ) {
             return
           }
    
  • playground/fs-serve/root/src/index.html+38 0 modified
    @@ -37,6 +37,10 @@ <h2>Unsafe /@fs/ Fetch</h2>
     <pre class="unsafe-fs-fetch"></pre>
     <pre class="unsafe-fs-fetch-raw-status"></pre>
     <pre class="unsafe-fs-fetch-raw"></pre>
    +<pre class="unsafe-fs-fetch-raw-query1-status"></pre>
    +<pre class="unsafe-fs-fetch-raw-query1"></pre>
    +<pre class="unsafe-fs-fetch-raw-query2-status"></pre>
    +<pre class="unsafe-fs-fetch-raw-query2"></pre>
     <pre class="unsafe-fs-fetch-8498-status"></pre>
     <pre class="unsafe-fs-fetch-8498"></pre>
     <pre class="unsafe-fs-fetch-8498-2-status"></pre>
    @@ -209,6 +213,40 @@ <h2>Denied</h2>
           console.error(e)
         })
     
    +  fetch(
    +    joinUrlSegments(
    +      base,
    +      joinUrlSegments('/@fs/', ROOT) + '/unsafe.json?import&raw??',
    +    ),
    +  )
    +    .then((r) => {
    +      text('.unsafe-fs-fetch-raw-query1-status', r.status)
    +      return r.json()
    +    })
    +    .then((data) => {
    +      text('.unsafe-fs-fetch-raw-query1', JSON.stringify(data))
    +    })
    +    .catch((e) => {
    +      console.error(e)
    +    })
    +
    +  fetch(
    +    joinUrlSegments(
    +      base,
    +      joinUrlSegments('/@fs/', ROOT) + '/unsafe.json?import&raw?&',
    +    ),
    +  )
    +    .then((r) => {
    +      text('.unsafe-fs-fetch-raw-query2-status', r.status)
    +      return r.json()
    +    })
    +    .then((data) => {
    +      text('.unsafe-fs-fetch-raw-query2', JSON.stringify(data))
    +    })
    +    .catch((e) => {
    +      console.error(e)
    +    })
    +
       // outside root with special characters #8498
       fetch(
         joinUrlSegments(
    
  • playground/fs-serve/__tests__/fs-serve.spec.ts+14 0 modified
    @@ -96,6 +96,20 @@ describe.runIf(isServe)('main', () => {
         expect(await page.textContent('.unsafe-fs-fetch-raw-status')).toBe('403')
       })
     
    +  test('unsafe fs fetch query 1', async () => {
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query1')).toBe('')
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query1-status')).toBe(
    +      '403',
    +    )
    +  })
    +
    +  test('unsafe fs fetch query 2', async () => {
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query2')).toBe('')
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query2-status')).toBe(
    +      '403',
    +    )
    +  })
    +
       test('unsafe fs fetch with special characters (#8498)', async () => {
         expect(await page.textContent('.unsafe-fs-fetch-8498')).toBe('')
         expect(await page.textContent('.unsafe-fs-fetch-8498-status')).toBe('404')
    
807d7f06d33a

fix: backport #19702, fs raw query with query separators (#19703)

https://github.com/vitejs/vite翠 / greenMar 24, 2025via ghsa
3 files changed · +65 2
  • packages/vite/src/node/server/middlewares/transform.ts+13 2 modified
    @@ -41,6 +41,7 @@ import { ensureServingAccess } from './static'
     const debugCache = createDebugger('vite:cache')
     
     const knownIgnoreList = new Set(['/', '/favicon.ico'])
    +const trailingQuerySeparatorsRE = /[?&]+$/
     
     /**
      * A middleware that short-circuits the middleware chain to serve cached transformed modules
    @@ -163,9 +164,19 @@ export function transformMiddleware(
             warnAboutExplicitPublicPathInUrl(url)
           }
     
    +      const urlWithoutTrailingQuerySeparators = url.replace(
    +        trailingQuerySeparatorsRE,
    +        '',
    +      )
           if (
    -        (rawRE.test(url) || urlRE.test(url)) &&
    -        !ensureServingAccess(url, server, res, next)
    +        (rawRE.test(urlWithoutTrailingQuerySeparators) ||
    +          urlRE.test(urlWithoutTrailingQuerySeparators)) &&
    +        !ensureServingAccess(
    +          urlWithoutTrailingQuerySeparators,
    +          server,
    +          res,
    +          next,
    +        )
           ) {
             return
           }
    
  • playground/fs-serve/root/src/index.html+38 0 modified
    @@ -37,6 +37,10 @@ <h2>Unsafe /@fs/ Fetch</h2>
     <pre class="unsafe-fs-fetch"></pre>
     <pre class="unsafe-fs-fetch-raw-status"></pre>
     <pre class="unsafe-fs-fetch-raw"></pre>
    +<pre class="unsafe-fs-fetch-raw-query1-status"></pre>
    +<pre class="unsafe-fs-fetch-raw-query1"></pre>
    +<pre class="unsafe-fs-fetch-raw-query2-status"></pre>
    +<pre class="unsafe-fs-fetch-raw-query2"></pre>
     <pre class="unsafe-fs-fetch-8498-status"></pre>
     <pre class="unsafe-fs-fetch-8498"></pre>
     <pre class="unsafe-fs-fetch-8498-2-status"></pre>
    @@ -209,6 +213,40 @@ <h2>Denied</h2>
           console.error(e)
         })
     
    +  fetch(
    +    joinUrlSegments(
    +      base,
    +      joinUrlSegments('/@fs/', ROOT) + '/unsafe.json?import&raw??',
    +    ),
    +  )
    +    .then((r) => {
    +      text('.unsafe-fs-fetch-raw-query1-status', r.status)
    +      return r.json()
    +    })
    +    .then((data) => {
    +      text('.unsafe-fs-fetch-raw-query1', JSON.stringify(data))
    +    })
    +    .catch((e) => {
    +      console.error(e)
    +    })
    +
    +  fetch(
    +    joinUrlSegments(
    +      base,
    +      joinUrlSegments('/@fs/', ROOT) + '/unsafe.json?import&raw?&',
    +    ),
    +  )
    +    .then((r) => {
    +      text('.unsafe-fs-fetch-raw-query2-status', r.status)
    +      return r.json()
    +    })
    +    .then((data) => {
    +      text('.unsafe-fs-fetch-raw-query2', JSON.stringify(data))
    +    })
    +    .catch((e) => {
    +      console.error(e)
    +    })
    +
       // outside root with special characters #8498
       fetch(
         joinUrlSegments(
    
  • playground/fs-serve/__tests__/fs-serve.spec.ts+14 0 modified
    @@ -96,6 +96,20 @@ describe.runIf(isServe)('main', () => {
         expect(await page.textContent('.unsafe-fs-fetch-raw-status')).toBe('403')
       })
     
    +  test('unsafe fs fetch query 1', async () => {
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query1')).toBe('')
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query1-status')).toBe(
    +      '403',
    +    )
    +  })
    +
    +  test('unsafe fs fetch query 2', async () => {
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query2')).toBe('')
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query2-status')).toBe(
    +      '403',
    +    )
    +  })
    +
       test('unsafe fs fetch with special characters (#8498)', async () => {
         expect(await page.textContent('.unsafe-fs-fetch-8498')).toBe('')
         expect(await page.textContent('.unsafe-fs-fetch-8498-status')).toBe('404')
    
80381c38d6f0

fix: fs raw query with query separators (#19702)

https://github.com/vitejs/vite翠 / greenMar 24, 2025via ghsa
3 files changed · +65 2
  • packages/vite/src/node/server/middlewares/transform.ts+13 2 modified
    @@ -43,6 +43,7 @@ import { ensureServingAccess } from './static'
     const debugCache = createDebugger('vite:cache')
     
     const knownIgnoreList = new Set(['/', '/favicon.ico'])
    +const trailingQuerySeparatorsRE = /[?&]+$/
     
     /**
      * A middleware that short-circuits the middleware chain to serve cached transformed modules
    @@ -169,9 +170,19 @@ export function transformMiddleware(
             warnAboutExplicitPublicPathInUrl(url)
           }
     
    +      const urlWithoutTrailingQuerySeparators = url.replace(
    +        trailingQuerySeparatorsRE,
    +        '',
    +      )
           if (
    -        (rawRE.test(url) || urlRE.test(url)) &&
    -        !ensureServingAccess(url, server, res, next)
    +        (rawRE.test(urlWithoutTrailingQuerySeparators) ||
    +          urlRE.test(urlWithoutTrailingQuerySeparators)) &&
    +        !ensureServingAccess(
    +          urlWithoutTrailingQuerySeparators,
    +          server,
    +          res,
    +          next,
    +        )
           ) {
             return
           }
    
  • playground/fs-serve/root/src/index.html+38 0 modified
    @@ -37,6 +37,10 @@ <h2>Unsafe /@fs/ Fetch</h2>
     <pre class="unsafe-fs-fetch"></pre>
     <pre class="unsafe-fs-fetch-raw-status"></pre>
     <pre class="unsafe-fs-fetch-raw"></pre>
    +<pre class="unsafe-fs-fetch-raw-query1-status"></pre>
    +<pre class="unsafe-fs-fetch-raw-query1"></pre>
    +<pre class="unsafe-fs-fetch-raw-query2-status"></pre>
    +<pre class="unsafe-fs-fetch-raw-query2"></pre>
     <pre class="unsafe-fs-fetch-8498-status"></pre>
     <pre class="unsafe-fs-fetch-8498"></pre>
     <pre class="unsafe-fs-fetch-8498-2-status"></pre>
    @@ -209,6 +213,40 @@ <h2>Denied</h2>
           console.error(e)
         })
     
    +  fetch(
    +    joinUrlSegments(
    +      base,
    +      joinUrlSegments('/@fs/', ROOT) + '/unsafe.json?import&raw??',
    +    ),
    +  )
    +    .then((r) => {
    +      text('.unsafe-fs-fetch-raw-query1-status', r.status)
    +      return r.json()
    +    })
    +    .then((data) => {
    +      text('.unsafe-fs-fetch-raw-query1', JSON.stringify(data))
    +    })
    +    .catch((e) => {
    +      console.error(e)
    +    })
    +
    +  fetch(
    +    joinUrlSegments(
    +      base,
    +      joinUrlSegments('/@fs/', ROOT) + '/unsafe.json?import&raw?&',
    +    ),
    +  )
    +    .then((r) => {
    +      text('.unsafe-fs-fetch-raw-query2-status', r.status)
    +      return r.json()
    +    })
    +    .then((data) => {
    +      text('.unsafe-fs-fetch-raw-query2', JSON.stringify(data))
    +    })
    +    .catch((e) => {
    +      console.error(e)
    +    })
    +
       // outside root with special characters #8498
       fetch(
         joinUrlSegments(
    
  • playground/fs-serve/__tests__/fs-serve.spec.ts+14 0 modified
    @@ -96,6 +96,20 @@ describe.runIf(isServe)('main', () => {
         expect(await page.textContent('.unsafe-fs-fetch-raw-status')).toBe('403')
       })
     
    +  test('unsafe fs fetch query 1', async () => {
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query1')).toBe('')
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query1-status')).toBe(
    +      '403',
    +    )
    +  })
    +
    +  test('unsafe fs fetch query 2', async () => {
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query2')).toBe('')
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query2-status')).toBe(
    +      '403',
    +    )
    +  })
    +
       test('unsafe fs fetch with special characters (#8498)', async () => {
         expect(await page.textContent('.unsafe-fs-fetch-8498')).toBe('')
         expect(await page.textContent('.unsafe-fs-fetch-8498-status')).toBe('404')
    
92ca12dc7911

fix: fs raw query with query separators (#19702)

https://github.com/vitejs/vite翠 / greenMar 24, 2025via ghsa
3 files changed · +65 2
  • packages/vite/src/node/server/middlewares/transform.ts+13 2 modified
    @@ -43,6 +43,7 @@ import { ensureServingAccess } from './static'
     const debugCache = createDebugger('vite:cache')
     
     const knownIgnoreList = new Set(['/', '/favicon.ico'])
    +const trailingQuerySeparatorsRE = /[?&]+$/
     
     /**
      * A middleware that short-circuits the middleware chain to serve cached transformed modules
    @@ -169,9 +170,19 @@ export function transformMiddleware(
             warnAboutExplicitPublicPathInUrl(url)
           }
     
    +      const urlWithoutTrailingQuerySeparators = url.replace(
    +        trailingQuerySeparatorsRE,
    +        '',
    +      )
           if (
    -        (rawRE.test(url) || urlRE.test(url)) &&
    -        !ensureServingAccess(url, server, res, next)
    +        (rawRE.test(urlWithoutTrailingQuerySeparators) ||
    +          urlRE.test(urlWithoutTrailingQuerySeparators)) &&
    +        !ensureServingAccess(
    +          urlWithoutTrailingQuerySeparators,
    +          server,
    +          res,
    +          next,
    +        )
           ) {
             return
           }
    
  • playground/fs-serve/root/src/index.html+38 0 modified
    @@ -37,6 +37,10 @@ <h2>Unsafe /@fs/ Fetch</h2>
     <pre class="unsafe-fs-fetch"></pre>
     <pre class="unsafe-fs-fetch-raw-status"></pre>
     <pre class="unsafe-fs-fetch-raw"></pre>
    +<pre class="unsafe-fs-fetch-raw-query1-status"></pre>
    +<pre class="unsafe-fs-fetch-raw-query1"></pre>
    +<pre class="unsafe-fs-fetch-raw-query2-status"></pre>
    +<pre class="unsafe-fs-fetch-raw-query2"></pre>
     <pre class="unsafe-fs-fetch-8498-status"></pre>
     <pre class="unsafe-fs-fetch-8498"></pre>
     <pre class="unsafe-fs-fetch-8498-2-status"></pre>
    @@ -209,6 +213,40 @@ <h2>Denied</h2>
           console.error(e)
         })
     
    +  fetch(
    +    joinUrlSegments(
    +      base,
    +      joinUrlSegments('/@fs/', ROOT) + '/unsafe.json?import&raw??',
    +    ),
    +  )
    +    .then((r) => {
    +      text('.unsafe-fs-fetch-raw-query1-status', r.status)
    +      return r.json()
    +    })
    +    .then((data) => {
    +      text('.unsafe-fs-fetch-raw-query1', JSON.stringify(data))
    +    })
    +    .catch((e) => {
    +      console.error(e)
    +    })
    +
    +  fetch(
    +    joinUrlSegments(
    +      base,
    +      joinUrlSegments('/@fs/', ROOT) + '/unsafe.json?import&raw?&',
    +    ),
    +  )
    +    .then((r) => {
    +      text('.unsafe-fs-fetch-raw-query2-status', r.status)
    +      return r.json()
    +    })
    +    .then((data) => {
    +      text('.unsafe-fs-fetch-raw-query2', JSON.stringify(data))
    +    })
    +    .catch((e) => {
    +      console.error(e)
    +    })
    +
       // outside root with special characters #8498
       fetch(
         joinUrlSegments(
    
  • playground/fs-serve/__tests__/fs-serve.spec.ts+14 0 modified
    @@ -96,6 +96,20 @@ describe.runIf(isServe)('main', () => {
         expect(await page.textContent('.unsafe-fs-fetch-raw-status')).toBe('403')
       })
     
    +  test('unsafe fs fetch query 1', async () => {
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query1')).toBe('')
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query1-status')).toBe(
    +      '403',
    +    )
    +  })
    +
    +  test('unsafe fs fetch query 2', async () => {
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query2')).toBe('')
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query2-status')).toBe(
    +      '403',
    +    )
    +  })
    +
       test('unsafe fs fetch with special characters (#8498)', async () => {
         expect(await page.textContent('.unsafe-fs-fetch-8498')).toBe('')
         expect(await page.textContent('.unsafe-fs-fetch-8498-status')).toBe('404')
    
f234b5744d8b

fix: fs raw query with query separators (#19702)

https://github.com/vitejs/vite翠 / greenMar 24, 2025via ghsa
3 files changed · +65 2
  • packages/vite/src/node/server/middlewares/transform.ts+13 2 modified
    @@ -43,6 +43,7 @@ import { ensureServingAccess } from './static'
     const debugCache = createDebugger('vite:cache')
     
     const knownIgnoreList = new Set(['/', '/favicon.ico'])
    +const trailingQuerySeparatorsRE = /[?&]+$/
     
     /**
      * A middleware that short-circuits the middleware chain to serve cached transformed modules
    @@ -169,9 +170,19 @@ export function transformMiddleware(
             warnAboutExplicitPublicPathInUrl(url)
           }
     
    +      const urlWithoutTrailingQuerySeparators = url.replace(
    +        trailingQuerySeparatorsRE,
    +        '',
    +      )
           if (
    -        (rawRE.test(url) || urlRE.test(url)) &&
    -        !ensureServingAccess(url, server, res, next)
    +        (rawRE.test(urlWithoutTrailingQuerySeparators) ||
    +          urlRE.test(urlWithoutTrailingQuerySeparators)) &&
    +        !ensureServingAccess(
    +          urlWithoutTrailingQuerySeparators,
    +          server,
    +          res,
    +          next,
    +        )
           ) {
             return
           }
    
  • playground/fs-serve/root/src/index.html+38 0 modified
    @@ -37,6 +37,10 @@ <h2>Unsafe /@fs/ Fetch</h2>
     <pre class="unsafe-fs-fetch"></pre>
     <pre class="unsafe-fs-fetch-raw-status"></pre>
     <pre class="unsafe-fs-fetch-raw"></pre>
    +<pre class="unsafe-fs-fetch-raw-query1-status"></pre>
    +<pre class="unsafe-fs-fetch-raw-query1"></pre>
    +<pre class="unsafe-fs-fetch-raw-query2-status"></pre>
    +<pre class="unsafe-fs-fetch-raw-query2"></pre>
     <pre class="unsafe-fs-fetch-8498-status"></pre>
     <pre class="unsafe-fs-fetch-8498"></pre>
     <pre class="unsafe-fs-fetch-8498-2-status"></pre>
    @@ -209,6 +213,40 @@ <h2>Denied</h2>
           console.error(e)
         })
     
    +  fetch(
    +    joinUrlSegments(
    +      base,
    +      joinUrlSegments('/@fs/', ROOT) + '/unsafe.json?import&raw??',
    +    ),
    +  )
    +    .then((r) => {
    +      text('.unsafe-fs-fetch-raw-query1-status', r.status)
    +      return r.json()
    +    })
    +    .then((data) => {
    +      text('.unsafe-fs-fetch-raw-query1', JSON.stringify(data))
    +    })
    +    .catch((e) => {
    +      console.error(e)
    +    })
    +
    +  fetch(
    +    joinUrlSegments(
    +      base,
    +      joinUrlSegments('/@fs/', ROOT) + '/unsafe.json?import&raw?&',
    +    ),
    +  )
    +    .then((r) => {
    +      text('.unsafe-fs-fetch-raw-query2-status', r.status)
    +      return r.json()
    +    })
    +    .then((data) => {
    +      text('.unsafe-fs-fetch-raw-query2', JSON.stringify(data))
    +    })
    +    .catch((e) => {
    +      console.error(e)
    +    })
    +
       // outside root with special characters #8498
       fetch(
         joinUrlSegments(
    
  • playground/fs-serve/__tests__/fs-serve.spec.ts+14 0 modified
    @@ -96,6 +96,20 @@ describe.runIf(isServe)('main', () => {
         expect(await page.textContent('.unsafe-fs-fetch-raw-status')).toBe('403')
       })
     
    +  test('unsafe fs fetch query 1', async () => {
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query1')).toBe('')
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query1-status')).toBe(
    +      '403',
    +    )
    +  })
    +
    +  test('unsafe fs fetch query 2', async () => {
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query2')).toBe('')
    +    expect(await page.textContent('.unsafe-fs-fetch-raw-query2-status')).toBe(
    +      '403',
    +    )
    +  })
    +
       test('unsafe fs fetch with special characters (#8498)', async () => {
         expect(await page.textContent('.unsafe-fs-fetch-8498')).toBe('')
         expect(await page.textContent('.unsafe-fs-fetch-8498-status')).toBe('404')
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.