VYPR
High severityNVD Advisory· Published Aug 18, 2022· Updated Aug 3, 2024

CVE-2022-35204

CVE-2022-35204

Description

Vitejs Vite before v2.9.13 was discovered to allow attackers to perform a directory traversal via a crafted URL to the victim's service.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Vite before v2.9.13 had a directory traversal bypass via URL-encoded path traversal sequences in the @fs endpoint.

Vulnerability

CVE-2022-35204 is a directory traversal vulnerability in Vite (Vitejs) versions prior to v2.9.13. The vulnerability arises from an incomplete fix for a previous path traversal issue (CVE-2022-2820). The server's @fs endpoint validates file access against an allow list, but the path validation was performed using decodeURI() instead of decodeURIComponent(). This discrepancy allowed attackers to bypass the allow list by URL-encoding path traversal sequences such as %2e%2e%2f (which decodes to ../), enabling access to files outside the intended serving directory [1][2][3].

Exploitation

An attacker could exploit this by crafting a URL with encoded traversal sequences targeted at the Vite dev server. For example, a request to http://localhost:3001/@fs/home/test/%2e%2e%2f%2e%2e%2fetc/passwd would bypass the allow list and access files outside the allowed scope [1]. The attack requires no authentication, as the Vite dev server typically listens on localhost. Tools like curl --path-as-is can be used to reproduce the issue, and the attack is effective across any Vite project running a vulnerable version [1].

Impact

Successful exploitation allows an attacker to read arbitrary files on the server's file system without authentication. This could lead to disclosure of sensitive data such as application source code, configuration files, or operating system files [1][2]. The vulnerability is classified with a medium severity (CVSS 5.3) due to the requirement that the attacker have network access to the dev server, which is often bound to localhost but can be exposed in some scenarios [2].

Mitigation

The vulnerability was addressed in Vite v2.9.13 by updating the URL decoding logic from decodeURI() to decodeURIComponent() in the serveStaticMiddleware and serveRawFsMiddleware [3][4]. A backported fix was provided for the v2.9.x release line [4]. Users are strongly advised to upgrade to v2.9.13 or later. No workarounds are documented, but restricting network exposure of the Vite dev server can reduce risk.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
vitenpm
< 2.9.132.9.13
vitenpm
>= 3.0.0-alpha.0, < 3.0.0-beta.43.0.0-beta.4

Affected products

2
  • Vitejs/Vitedescription
  • ghsa-coords
    Range: < 2.9.13

Patches

2
e109d64331d9

fix: backport #8804, /@fs/ dir traversal with escaped chars (fixes #8498) (#8805)

https://github.com/vitejs/vite翠 / greenJun 27, 2022via ghsa
3 files changed · +41 2
  • packages/playground/fs-serve/root/src/index.html+27 0 modified
    @@ -17,6 +17,8 @@ <h2>Safe Fetch Subdirectory</h2>
     <h2>Unsafe Fetch</h2>
     <pre class="unsafe-fetch-status"></pre>
     <pre class="unsafe-fetch"></pre>
    +<pre class="unsafe-fetch-8498-status"></pre>
    +<pre class="unsafe-fetch-8498"></pre>
     
     <h2>Safe /@fs/ Fetch</h2>
     <pre class="safe-fs-fetch-status"></pre>
    @@ -27,6 +29,8 @@ <h2>Safe /@fs/ Fetch</h2>
     <h2>Unsafe /@fs/ Fetch</h2>
     <pre class="unsafe-fs-fetch-status"></pre>
     <pre class="unsafe-fs-fetch"></pre>
    +<pre class="unsafe-fs-fetch-8498-status"></pre>
    +<pre class="unsafe-fs-fetch-8498"></pre>
     
     <h2>Nested Entry</h2>
     <pre class="nested-entry"></pre>
    @@ -83,6 +87,19 @@ <h2>Denied</h2>
           console.error(e)
         })
     
    +  // outside of allowed dir with special characters #8498
    +  fetch('/src/%2e%2e%2funsafe%2etxt')
    +    .then((r) => {
    +      text('.unsafe-fetch-8498-status', r.status)
    +      return r.text()
    +    })
    +    .then((data) => {
    +      text('.unsafe-fetch-8498', data)
    +    })
    +    .catch((e) => {
    +      console.error(e)
    +    })
    +
       // imported before, should be treated as safe
       fetch('/@fs/' + ROOT + '/safe.json')
         .then((r) => {
    @@ -106,6 +123,16 @@ <h2>Denied</h2>
           console.error(e)
         })
     
    +  // outside root with special characters #8498
    +  fetch('/@fs/' + ROOT + '/root/src/%2e%2e%2f%2e%2e%2funsafe%2ejson')
    +    .then((r) => {
    +      text('.unsafe-fs-fetch-8498-status', r.status)
    +      return r.json()
    +    })
    +    .then((data) => {
    +      text('.unsafe-fs-fetch-8498', JSON.stringify(data))
    +    })
    +
       // not imported before, inside root with special characters, treated as safe
       fetch(
         '/@fs/' +
    
  • packages/playground/fs-serve/__tests__/fs-serve.spec.ts+12 0 modified
    @@ -37,6 +37,13 @@ describe('main', () => {
           expect(await page.textContent('.unsafe-fetch-status')).toBe('403')
         })
     
    +    test('unsafe fetch with special characters (#8498)', async () => {
    +      expect(await page.textContent('.unsafe-fetch-8498')).toMatch(
    +        '403 Restricted'
    +      )
    +      expect(await page.textContent('.unsafe-fetch-8498-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')
    @@ -54,6 +61,11 @@ describe('main', () => {
           expect(await page.textContent('.unsafe-fs-fetch-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('403')
    +    })
    +
         test('nested entry', async () => {
           expect(await page.textContent('.nested-entry')).toBe('foobar')
         })
    
  • packages/vite/src/node/server/middlewares/static.ts+2 2 modified
    @@ -78,7 +78,7 @@ export function serveStaticMiddleware(
           return next()
         }
     
    -    const url = decodeURI(req.url!)
    +    const url = decodeURIComponent(req.url!)
     
         // apply aliases to static requests as well
         let redirected: string | undefined
    @@ -121,7 +121,7 @@ export function serveRawFsMiddleware(
     
       // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
       return function viteServeRawFsMiddleware(req, res, next) {
    -    let url = decodeURI(req.url!)
    +    let url = decodeURIComponent(req.url!)
         // In some cases (e.g. linked monorepos) files outside of root will
         // reference assets that are also out of served root. In such cases
         // the paths are rewritten to `/@fs/` prefixed paths and must be served by
    
6851009e6725

fix: /@fs/ dir traversal with escaped chars (fixes #8498) (#8804)

https://github.com/vitejs/vite翠 / greenJun 27, 2022via ghsa
3 files changed · +41 2
  • packages/vite/src/node/server/middlewares/static.ts+2 2 modified
    @@ -80,7 +80,7 @@ export function serveStaticMiddleware(
           return next()
         }
     
    -    const url = decodeURI(req.url!)
    +    const url = decodeURIComponent(req.url!)
     
         // apply aliases to static requests as well
         let redirected: string | undefined
    @@ -123,7 +123,7 @@ export function serveRawFsMiddleware(
     
       // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
       return function viteServeRawFsMiddleware(req, res, next) {
    -    let url = decodeURI(req.url!)
    +    let url = decodeURIComponent(req.url!)
         // In some cases (e.g. linked monorepos) files outside of root will
         // reference assets that are also out of served root. In such cases
         // the paths are rewritten to `/@fs/` prefixed paths and must be served by
    
  • playground/fs-serve/root/src/index.html+27 0 modified
    @@ -17,6 +17,8 @@ <h2>Safe Fetch Subdirectory</h2>
     <h2>Unsafe Fetch</h2>
     <pre class="unsafe-fetch-status"></pre>
     <pre class="unsafe-fetch"></pre>
    +<pre class="unsafe-fetch-8498-status"></pre>
    +<pre class="unsafe-fetch-8498"></pre>
     
     <h2>Safe /@fs/ Fetch</h2>
     <pre class="safe-fs-fetch-status"></pre>
    @@ -27,6 +29,8 @@ <h2>Safe /@fs/ Fetch</h2>
     <h2>Unsafe /@fs/ Fetch</h2>
     <pre class="unsafe-fs-fetch-status"></pre>
     <pre class="unsafe-fs-fetch"></pre>
    +<pre class="unsafe-fs-fetch-8498-status"></pre>
    +<pre class="unsafe-fs-fetch-8498"></pre>
     
     <h2>Nested Entry</h2>
     <pre class="nested-entry"></pre>
    @@ -83,6 +87,19 @@ <h2>Denied</h2>
           console.error(e)
         })
     
    +  // outside of allowed dir with special characters #8498
    +  fetch('/src/%2e%2e%2funsafe%2etxt')
    +    .then((r) => {
    +      text('.unsafe-fetch-8498-status', r.status)
    +      return r.text()
    +    })
    +    .then((data) => {
    +      text('.unsafe-fetch-8498', data)
    +    })
    +    .catch((e) => {
    +      console.error(e)
    +    })
    +
       // imported before, should be treated as safe
       fetch('/@fs/' + ROOT + '/safe.json')
         .then((r) => {
    @@ -106,6 +123,16 @@ <h2>Denied</h2>
           console.error(e)
         })
     
    +  // outside root with special characters #8498
    +  fetch('/@fs/' + ROOT + '/root/src/%2e%2e%2f%2e%2e%2funsafe%2ejson')
    +    .then((r) => {
    +      text('.unsafe-fs-fetch-8498-status', r.status)
    +      return r.json()
    +    })
    +    .then((data) => {
    +      text('.unsafe-fs-fetch-8498', JSON.stringify(data))
    +    })
    +
       // not imported before, inside root with special characters, treated as safe
       fetch(
         '/@fs/' +
    
  • playground/fs-serve/__tests__/fs-serve.spec.ts+12 0 modified
    @@ -35,6 +35,13 @@ describe.runIf(isServe)('main', () => {
         expect(await page.textContent('.unsafe-fetch-status')).toBe('403')
       })
     
    +  test('unsafe fetch with special characters (#8498)', async () => {
    +    expect(await page.textContent('.unsafe-fetch-8498')).toMatch(
    +      '403 Restricted'
    +    )
    +    expect(await page.textContent('.unsafe-fetch-8498-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')
    @@ -52,6 +59,11 @@ describe.runIf(isServe)('main', () => {
         expect(await page.textContent('.unsafe-fs-fetch-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('403')
    +  })
    +
       test('nested entry', async () => {
         expect(await page.textContent('.nested-entry')).toBe('foobar')
       })
    

Vulnerability mechanics

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

References

7

News mentions

0

No linked articles in our index yet.