GHSA-g7r4-m6w7-qqqr
esbuild allows arbitrary file read when running the development server on Windows
Details
### Summary
The development server contains a path traversal vulnerability on Windows when serving files from `servedir`.
Due to the use of `path.Clean()` (which only normalizes forward-slash `/` separators) instead of a Windows-aware path normalization function, it is possible to craft requests using backslashes (`\`) that bypass the intended directory containment logic. An attacker can escape the configured `servedir` root and access arbitrary files on the filesystem. This issue affects Windows environments only.
### Details
The request path is sanitized using: ```go // https://github.com/evanw/esbuild/blob/v0.27.3/pkg/api/serve_other.go#L165 queryPath := path.Clean(req.URL.Path)[1:] ```
However: - `path.Clean()` is POSIX-style and only understands `/` (docs: `https://pkg.go.dev/path#Clean`) - On Windows, `\` is a valid path separator - `path.Clean()` does not treat `\` as a separator
Later, the server constructs the absolute path: ```go // https://github.com/evanw/esbuild/blob/v0.27.3/pkg/api/serve_other.go#L221 absPath := h.fs.Join(h.servedir, queryPath) ```
If `queryPath` contains sequences such as: ``` ..\..\..\..\..\..\..\Windows\system.ini ```
`path.Clean()` will not normalize them, but the Windows filesystem will interpret `\` as directory separators when resolving `absPath`. Because the implementation does not verify that the final resolved path remains within `servedir`, it allows directory traversal outside the intended root directory. ### Vulnerable Code
```go // https://github.com/evanw/esbuild/blob/v0.27.3/pkg/api/serve_other.go#L165 queryPath := path.Clean(req.URL.Path)[1:] .... // Check for a file in the "servedir" directory if h.servedir != "" && kind != fs.FileEntry { absPath := h.fs.Join(h.servedir, queryPath) if absDir := h.fs.Dir(absPath); absDir != absPath { if entries, err, _ := h.fs.ReadDirectory(absDir); err == nil { if entry, _ := entries.Get(h.fs.Base(absPath)); entry != nil && entry.Kind(h.fs) == fs.FileEntry { .... ```
### Steps to reproduce
``` npm install --save-exact --save-dev esbuild
echo "console.log(1)" > app.js
.\node_modules\.bin\esbuild --version 0.27.3
.\node_modules\.bin\esbuild app.js --bundle --outdir=www --servedir=www --watch
curl -i --path-as-is "http://localhost:8000/..\..\..\..\..\..\..\Windows\system.ini" <content of Windows\system.ini> ```
### Impact
- Arbitrary file read on Windows - Exposure of sensitive files
Are you affected?
Enter the version of the package you're using.