esbuild allows arbitrary file read when running the development server on Windows
Affected releases · esbuild
- 0.28.x0.28.0Fixed in 0.28.1
- 0.27.x0.27.3 – 0.27.7No fix on this line yet
On 0.28.x, the newest line:
npm install esbuild@0.28.1Not affected
0.0.0 – 0.27.2
475 releases
Affected
0.27.3 – 0.28.0
5 releases
Fixed
0.28.1 – 0.28.2
2 releases, latest
What happens
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:
// 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 separatorpath.Clean()does not treat\as a separator
Later, the server constructs the absolute path:
// 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
// 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