CVE-2025-59342
Description
esm.sh is a nobuild content delivery network(CDN) for modern web development. In 136 and earlier, a path-traversal flaw in the handling of the X-Zone-Id HTTP header allows an attacker to cause the application to write files outside the intended storage location. The header value is used to build a filesystem path but is not properly canonicalized or restricted to the application’s storage base directory. As a result, supplying ../ sequences in X-Zone-Id causes files to be written to arbitrary directories. Version 136.1 contains a patch.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/esm-dev/esm.shGo | < 136.1 | 136.1 |
Affected products
1Patches
1833a29f42aebFix arbitrary file write via path traversal in `X-Zone-Id` header (#1208)
1 file changed · +27 −22
server/router.go+27 −22 modified@@ -112,8 +112,13 @@ func esmRouter(db Database, esmStorage storage.Storage, logger *log.Logger) rex. fmt.Fprintf(h, "%v", options.Minify) hash := hex.EncodeToString(h.Sum(nil)) + zoneId := ctx.R.Header.Get("X-Zone-Id") + if zoneId != "" && !valid.IsDomain(zoneId) { + zoneId = "" + } + savePath := normalizeSavePath(zoneId, fmt.Sprintf("modules/transform/%s.mjs", hash)) + // if previous build exists, return it directly - savePath := normalizeSavePath(ctx.R.Header.Get("X-Zone-Id"), fmt.Sprintf("modules/transform/%s.mjs", hash)) if file, _, err := esmStorage.Get(savePath); err == nil { data, err := io.ReadAll(file) file.Close() @@ -408,7 +413,11 @@ func esmRouter(db Database, esmStorage storage.Storage, logger *log.Logger) rex. if len(hash) != 40 || !valid.IsHexString(hash) { return rex.Status(404, "Not Found") } - savePath := normalizeSavePath(ctx.R.Header.Get("X-Zone-Id"), fmt.Sprintf("modules/transform/%s.%s", hash, ext)) + zoneId := ctx.R.Header.Get("X-Zone-Id") + if zoneId != "" && !valid.IsDomain(zoneId) { + zoneId = "" + } + savePath := normalizeSavePath(zoneId, fmt.Sprintf("modules/transform/%s.%s", hash, ext)) f, fi, err := esmStorage.Get(savePath) if err != nil { return rex.Status(500, err.Error()) @@ -485,27 +494,23 @@ func esmRouter(db Database, esmStorage storage.Storage, logger *log.Logger) rex. npmrc = DefaultNpmRC() } - zoneIdHeader := ctx.R.Header.Get("X-Zone-Id") - if zoneIdHeader != "" { - if !valid.IsDomain(zoneIdHeader) { - zoneIdHeader = "" - } else { - var scopeName string - if pkgName := toPackageName(pathname[1:]); strings.HasPrefix(pkgName, "@") { - scopeName = pkgName[:strings.Index(pkgName, "/")] - } - if scopeName != "" { - reg, ok := npmrc.ScopedRegistries[scopeName] - if !ok || (reg.Registry == jsrRegistry && reg.Token == "" && (reg.User == "" || reg.Password == "")) { - zoneIdHeader = "" - } - } else if npmrc.Registry == npmRegistry && npmrc.Token == "" && (npmrc.User == "" || npmrc.Password == "") { - zoneIdHeader = "" + zoneId := ctx.R.Header.Get("X-Zone-Id") + if zoneId != "" { + var scopeName string + if pkgName := toPackageName(pathname[1:]); strings.HasPrefix(pkgName, "@") { + scopeName = pkgName[:strings.Index(pkgName, "/")] + } + if scopeName != "" { + reg, ok := npmrc.ScopedRegistries[scopeName] + if !ok || (reg.Registry == jsrRegistry && reg.Token == "" && (reg.User == "" || reg.Password == "")) { + zoneId = "" } + } else if npmrc.Registry == npmRegistry && npmrc.Token == "" && (npmrc.User == "" || npmrc.Password == "") { + zoneId = "" } } - if zoneIdHeader != "" { - npmrc.zoneId = zoneIdHeader + if zoneId != "" && valid.IsDomain(zoneId) { + npmrc.zoneId = zoneId } if strings.HasPrefix(pathname, "/http://") || strings.HasPrefix(pathname, "/https://") { @@ -560,7 +565,7 @@ func esmRouter(db Database, esmStorage storage.Storage, logger *log.Logger) rex. h.Write([]byte(ctxParam)) h.Write([]byte(target)) h.Write([]byte(v)) - savePath := normalizeSavePath(zoneIdHeader, path.Join("modules/x", hex.EncodeToString(h.Sum(nil))+".css")) + savePath := normalizeSavePath(npmrc.zoneId, path.Join("modules/x", hex.EncodeToString(h.Sum(nil))+".css")) r, fi, err := esmStorage.Get(savePath) if err != nil && err != storage.ErrNotFound { return rex.Status(500, err.Error()) @@ -704,7 +709,7 @@ func esmRouter(db Database, esmStorage storage.Storage, logger *log.Logger) rex. h.Write([]byte(im)) h.Write([]byte(target)) h.Write([]byte(v)) - savePath := normalizeSavePath(zoneIdHeader, path.Join("modules/x", hex.EncodeToString(h.Sum(nil))+".mjs")) + savePath := normalizeSavePath(npmrc.zoneId, path.Join("modules/x", hex.EncodeToString(h.Sum(nil))+".mjs")) content, fi, err := esmStorage.Get(savePath) if err != nil && err != storage.ErrNotFound { return rex.Status(500, err.Error())
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
7- github.com/advisories/GHSA-g2h5-cvvr-7gmwghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2025-59342ghsaADVISORY
- github.com/esm-dev/esm.sh/blob/main/server/router.gonvdWEB
- github.com/esm-dev/esm.sh/blob/main/server/router.gonvdWEB
- github.com/esm-dev/esm.sh/commit/833a29f42aeb0acbd7089a71be11dd0a292d3151nvdWEB
- github.com/esm-dev/esm.sh/security/advisories/GHSA-g2h5-cvvr-7gmwnvdWEB
- pkg.go.dev/vuln/GO-2025-3967ghsaWEB
News mentions
0No linked articles in our index yet.