GHSA-g3c2-rjq2-c763: esm.sh: web.NewHandler allows arbitrary file read via encoded dot-segment path traversal

  • Severity: HIGH
  • CVE: CVE-2026-102657
  • Ecosystems: go
  • Packages: github.com/esm-dev/esm.sh/web

### Summary

The documented `web.NewHandler` static file server can read files outside `Config.AppDir` when the request path contains URL-encoded dot-segments such as `%2e%2e`. Go’s `ServeMux` redirects plain `/../` paths, but it passes encoded dot-segments to the handler after decoding `r.URL.Path`. The handler then calls `filepath.Join(s.config.AppDir, r.URL.Path)` without checking that the result remains under `AppDir`, allowing unauthenticated arbitrary file reads with the server process privileges.

### Details

`web/README.md:34-40` documents a network-facing setup:

“`go
http.Handle(“GET /”, web.NewHandler(web.Config{
AppDir: “/path/to/webapp”,
Fallback: “/index.html”,
Dev: false,
}))
log.Fatal(http.ListenAndServe(“:8080”, nil))
“`

In `web/handler.go:62-75`, the handler takes `r.URL.Path` and joins it directly with `s.config.AppDir`:

“`go
pathname := r.URL.Path
filename := filepath.Join(s.config.AppDir, pathname)
fi, err := os.Lstat(filename)
“`

Later the same resolved filename is opened and copied to the response in `web/handler.go:150-166` for non-HTML/static files. There is no `filepath.IsLocal`, cleaned-relative-path, absolute-root prefix, or symlink-aware containment check before opening the file.

A plain `/../secret.txt` path is redirected by Go’s `ServeMux`, but an encoded path such as `/%2e%2e/secret.txt` is decoded into `r.URL.Path == “/../secret.txt”` and reaches the handler. On Linux, `filepath.Join(“/tmp/root/app”, “/../secret.txt”)` resolves to…

Source: GitHub Security Advisories · 2026-11-09