GitHub Security Advisory: GHSA-p2v6-84h2-5x4r
Package: github.com/esm-dev/esm.sh (GO)
Severity: HIGH (CVSS 8.6)
CWE: CWE-918: Server-Side Request Forgery (SSRF)
Vulnerable versions: < 0.0.0[PII_REDACTED]4[PII_REDACTED]c4cfa
Patched in: 0.0.0[PII_REDACTED]4[PII_REDACTED]c4cfa

Summary: esm.sh has SSRF localhost/private-network bypass in `/http(s)` module route

Details:
### Summary
An SSRF vulnerability (CWE-918) exists in esm.sh’s `/http(s)` fetch route.  
The service tries to block localhost/internal targets, but the validation is based on hostname string checks and can be bypassed using DNS alias domains (for example, `127.0.0.1.nip.io` resolving to `127.0.0.1`).  
This allows an external requester to make the esm.sh server fetch internal localhost services.  
Severity:  High (depending on deployment network exposure).

### Details
The vulnerable flow starts at the route handling user-controlled remote URLs:

- `server/router.go:532`
  - Accepts paths beginning with `/http://` or `/https://`.
 ```go
if strings.HasPrefix(pathname, "/http://") || strings.HasPrefix(pathname, "/https://") {
	query := ctx.Query()
	modUrl, err := url.Parse(pathname[1:])
	if err != nil {
		ctx.SetHeader("Cache-Control", ccImmutable)
		return rex.Status(400, "Invalid URL")
	}
	if modUrl.Scheme != "http" && modUrl.Scheme != "https" {
		ctx.SetHeader("Cache-Control", ccImmutable)
		return rex.Status(400, "Invalid URL")
	}
	modUrlStr := modUrl.String()

	// disallow localhost or ip address for production
	if !DEBUG {
		hostname := modUrl.Hostname()
		if isLocalhost(hostname) || !valid.IsDomain(hostname) || modUrl.Host == ctx.R.Host {
			ctx.SetHeader("Cache-Control", ccImmutable)
			return rex.Status(400, "Invalid URL")
		}
	}
```

The internal-target block is string-based:

- `server/router.go:545`
 ```go
			// disallow localhost or ip address for production
			if !DEBUG {
				hostname := modUrl.Hostname()
				if isLocalhost(hostname) || !valid.IsDomain(hostname) || modUrl.Host == ctx.R.Host {
					ctx.SetHeader("Cache-Control", ccImmutable)
					return rex.Status(400, "Invalid URL")
				}
			}
```

Localhost detection itself is limited to hostname patterns:

- `server/utils.go:72`
  - `isLocalhost(...)` checks values like `localhost`, `127.0.0.1`, and `192.168.*`.
  - It does **not** validate the resolved destination IP after DNS resolution.
```go
func isLocalhost(hostname string) bool {
	return hostname == "localhost" || strings.HasSuffix(hostname, ".localhost") || hostname == "127.0.0.1" || (valid.IsIPv4(hostname) && strings.HasPrefix(hostname, "192.168."))
}
```

Fetch proceeds with host-string allowlisting:

- `server/router.go:595-596`
  - `allowedHosts[modUrl.Host] = struct{}{}` then `fetch.NewClient(...allowedHosts)`
```go
allowedHosts := map[string]struct{}{}
allowedHosts[modUrl.Host] = struct{}{}
fetchClient, recycle := fetch.NewClient(ctx.UserAgent(), 15, false, allowedHosts)
defer recycle()
```

- `internal/fetch/fetch.go:49`
  - Host allowlist compares host strings, not resolved IP class.
```go
func (c *FetchClient) Fetch(url *url.URL, header http.Header) (resp *http.Response, err error) {
	if c.allowedHosts != nil {
		if _, ok := c.allowedHosts[url.Host]; !ok {
			return nil, errors.New("host not allowed: " + url.Host)
		}
	}
	if c.userAgent != "" {
		if header == nil {
			header = make(http.Header)
		}
		header.Set("User