VYPR
Medium severityOSV Advisory· Published Apr 10, 2025· Updated Apr 15, 2026

CVE-2025-32395

CVE-2025-32395

Description

Vite is a frontend tooling framework for javascript. Prior to 6.2.6, 6.1.5, 6.0.15, 5.4.18, and 4.5.13, the contents of arbitrary files can be returned to the browser if the dev server is running on Node or Bun. HTTP 1.1 spec (RFC 9112) does not allow # in request-target. Although an attacker can send such a request. For those requests with an invalid request-line (it includes request-target), the spec recommends to reject them with 400 or 301. The same can be said for HTTP 2. On Node and Bun, those requests are not rejected internally and is passed to the user land. For those requests, the value of http.IncomingMessage.url contains #. Vite assumed req.url won't contain # when checking server.fs.deny, allowing those kinds of requests to bypass the check. Only apps explicitly exposing the Vite dev server to the network (using --host or server.host config option) and running the Vite dev server on runtimes that are not Deno (e.g. Node, Bun) are affected. This vulnerability is fixed in 6.2.6, 6.1.5, 6.0.15, 5.4.18, and 4.5.13.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
vitenpm
>= 6.2.0, < 6.2.66.2.6
vitenpm
>= 6.1.0, < 6.1.56.1.5
vitenpm
>= 6.0.0, < 6.0.156.0.15
vitenpm
>= 5.0.0, < 5.4.185.4.18
vitenpm
< 4.5.134.5.13

Affected products

1
  • Range: create-app@1.0.0, create-app@1.0.1, create-app@1.0.2, …

Patches

6
175a83909f02

fix: reject requests with `#` in request-target (#19830)

https://github.com/vitejs/vite翠 / greenApr 10, 2025via ghsa
3 files changed · +99 0
  • packages/vite/src/node/server/index.ts+4 0 modified
    @@ -98,6 +98,7 @@ import { transformRequest } from './transformRequest'
     import { searchForPackageRoot, searchForWorkspaceRoot } from './searchRoot'
     import type { DevEnvironment } from './environment'
     import { hostCheckMiddleware } from './middlewares/hostCheck'
    +import { rejectInvalidRequestMiddleware } from './middlewares/rejectInvalidRequest'
     
     export interface ServerOptions extends CommonServerOptions {
       /**
    @@ -864,6 +865,9 @@ export async function _createServer(
         middlewares.use(timeMiddleware(root))
       }
     
    +  // disallows request that contains `#` in the URL
    +  middlewares.use(rejectInvalidRequestMiddleware())
    +
       // cors
       const { cors } = serverConfig
       if (cors !== false) {
    
  • packages/vite/src/node/server/middlewares/rejectInvalidRequest.ts+20 0 added
    @@ -0,0 +1,20 @@
    +import type { Connect } from 'dep-types/connect'
    +
    +export function rejectInvalidRequestMiddleware(): Connect.NextHandleFunction {
    +  // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
    +  return function viteRejectInvalidRequestMiddleware(req, res, next) {
    +    // HTTP spec does not allow `#` in the request-target
    +    // (HTTP 1.1: https://datatracker.ietf.org/doc/html/rfc9112#section-3.2)
    +    // (HTTP 2: https://datatracker.ietf.org/doc/html/rfc9113#section-8.3.1-2.4.1)
    +    // But Node.js allows those requests.
    +    // Our middlewares don't expect `#` to be included in `req.url`, especially the `server.fs.deny` checks.
    +    if (req.url?.includes('#')) {
    +      // HTTP 1.1 spec recommends sending 400 Bad Request
    +      // (https://datatracker.ietf.org/doc/html/rfc9112#section-3.2-4)
    +      res.writeHead(400)
    +      res.end()
    +      return
    +    }
    +    return next()
    +  }
    +}
    
  • playground/fs-serve/__tests__/fs-serve.spec.ts+75 0 modified
    @@ -1,3 +1,6 @@
    +import net from 'node:net'
    +import path from 'node:path'
    +import { fileURLToPath } from 'node:url'
     import fetch from 'node-fetch'
     import {
       afterEach,
    @@ -12,6 +15,8 @@ import WebSocket from 'ws'
     import testJSON from '../safe.json'
     import { browser, isServe, page, viteServer, viteTestUrl } from '~utils'
     
    +const __dirname = path.dirname(fileURLToPath(import.meta.url))
    +
     const getViteTestIndexHtmlUrl = () => {
       const srcPrefix = viteTestUrl.endsWith('/') ? '' : '/'
       // NOTE: viteTestUrl is set lazily
    @@ -391,3 +396,73 @@ describe('cross origin', () => {
         )
       })
     })
    +
    +describe.runIf(isServe)('invalid request', () => {
    +  const sendRawRequest = async (baseUrl: string, requestTarget: string) => {
    +    return new Promise<string>((resolve, reject) => {
    +      const parsedUrl = new URL(baseUrl)
    +
    +      const buf: Buffer[] = []
    +      const client = net.createConnection(
    +        { port: +parsedUrl.port, host: parsedUrl.hostname },
    +        () => {
    +          client.write(
    +            [
    +              `GET ${encodeURI(requestTarget)} HTTP/1.1`,
    +              `Host: ${parsedUrl.host}`,
    +              'Connection: Close',
    +              '\r\n',
    +            ].join('\r\n'),
    +          )
    +        },
    +      )
    +      client.on('data', (data) => {
    +        buf.push(data)
    +      })
    +      client.on('end', (hadError) => {
    +        if (!hadError) {
    +          resolve(Buffer.concat(buf).toString())
    +        }
    +      })
    +      client.on('error', (err) => {
    +        reject(err)
    +      })
    +    })
    +  }
    +
    +  const root = path
    +    .resolve(__dirname.replace('playground', 'playground-temp'), '..')
    +    .replace(/\\/g, '/')
    +
    +  test('request with sendRawRequest should work', async () => {
    +    const response = await sendRawRequest(viteTestUrl, '/src/safe.txt')
    +    expect(response).toContain('HTTP/1.1 200 OK')
    +    expect(response).toContain('KEY=safe')
    +  })
    +
    +  test('request with sendRawRequest should work with /@fs/', async () => {
    +    const response = await sendRawRequest(
    +      viteTestUrl,
    +      path.posix.join('/@fs/', root, 'root/src/safe.txt'),
    +    )
    +    expect(response).toContain('HTTP/1.1 200 OK')
    +    expect(response).toContain('KEY=safe')
    +  })
    +
    +  test('should reject request that has # in request-target', async () => {
    +    const response = await sendRawRequest(
    +      viteTestUrl,
    +      '/src/safe.txt#/../../unsafe.txt',
    +    )
    +    expect(response).toContain('HTTP/1.1 400 Bad Request')
    +  })
    +
    +  test('should reject request that has # in request-target with /@fs/', async () => {
    +    const response = await sendRawRequest(
    +      viteTestUrl,
    +      path.posix.join('/@fs/', root, 'root/src/safe.txt') +
    +        '#/../../unsafe.txt',
    +    )
    +    expect(response).toContain('HTTP/1.1 400 Bad Request')
    +  })
    +})
    

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

4

News mentions

0

No linked articles in our index yet.