esbuild 0.28.1: First Release in Two Months Ships a High-Severity Deno RCE, a Windows Path Traversal, and a `using` Disposal Bug

esbuild 0.28.1: First Release in Two Months Ships a High-Severity Deno RCE, a Windows Path Traversal, and a `using` Disposal Bug

lschvn

esbuild v0.28.1, shipped on June 11, 2026, is the bundler's first release since v0.28.0 on April 2. The two-month gap is unusual for a project that normally cuts a release every few weeks, and 0.28.1 spends the budget on three things that each warrant an upgrade on their own: a high-severity remote code execution in the Deno API, a Windows-only dev-server path traversal, and a minifier correctness bug that silently broke the using and await using resource-disposal syntax.

Because esbuild sits underneath Vite's dependency optimizer and TS/JSX pipeline, a lot of projects inherit these fixes transitively. Here is what changed and who needs to act.

A High-severity RCE in the Deno API

The most serious item is GHSA-gv7w-rqvm-qjhr, rated High with CVSS 8.1. The Deno distribution of esbuild (lib/deno/mod.ts) downloads the native esbuild binary from an npm registry and writes it to disk with executable permissions (0o755) without verifying its contents. The Node installer already had a binaryIntegrityCheck() that compares a SHA-256 hash of the downloaded executable against expected values baked into package.json; the Deno path never got the equivalent.

The exploit path is the NPM_CONFIG_REGISTRY environment variable. The Deno module builds its download URL from that variable, so anyone who can set it, in a CI pipeline, a shared development machine, or a corporate network that proxies npm through a custom registry, can serve a trojaned binary that esbuild will happily execute. That is the same class of supply-chain trust failure that produced the recent npm shai-hulud account-takeover incident: the registry is trusted by default and the consumer has no second check.

0.28.1 ports the SHA-256 integrity check to the Deno installer. A binary whose hash does not match the expected value now fails with an error instead of running. Note that esbuild's Deno API still installs from registry.npmjs.org by default and still honors NPM_CONFIG_REGISTRY; the fix is the verification, not a change of source.

A Windows path traversal in the dev server

The second advisory, GHSA-g7r4-m6w7-qqqr, is rated Low (CVSS 2.5) but is a clean illustration of a classic bug. esbuild's local development server sanitized incoming request paths with Go's path.Clean(). That function is POSIX-only: it understands forward slashes but treats a backslash as an ordinary character. On Windows, where \ is a valid path separator, a crafted request could therefore escape the configured serve directory and read arbitrary files.

The root cause is one line of code:

queryPath := path.Clean(req.URL.Path)[1:]

0.28.1 disallows backslashes in dev-server request paths entirely. The exposure is narrow: it requires the dev server (esbuild.context().serve() or the --serve flag) running on Windows and reachable by an attacker. Production bundling and one-shot builds are unaffected, as are macOS and Linux hosts.

The using and await using minifier bug

The third fix is the one most application developers should actually audit their output for. Issue #4482: esbuild's minifier sometimes inlined a using or await using declaration into its later use, which drops the binding and means Symbol.dispose / Symbol.asyncDispose never runs. The failure looks like this:

// Original code
{
  using x = new Resource()
  x.activate()
}

// Old esbuild output (with --minify)
new Resource().activate();

// Fixed output (0.28.1)
{using e=new Resource;e.activate()}

The old output leaks the resource every time, because there is no using left to trigger disposal at the end of the block. The bug exists because the minifier's inlining pass was written for let and const, then excluded var, and was never updated when the explicit resource management syntax added two more declaration kinds. If you ship minified production builds and use using for file handles, database connections, locks, or any AsyncDisposable, this is a silent correctness regression worth grepping your build output for.

Smaller correctness fixes

The rest of 0.28.1 is a cluster of correctness fixes that mostly affect bundling edge cases:

  • Re-thrown module errors (#4461, #4467). If a module throws during evaluation, the spec requires every subsequent import() or require() of that module to throw the same error. esbuild previously only preserved the error for the first call. This matters for retry logic and dynamic-import error handling.
  • new operator wrapping (#4477). Complex targets of new, specifically tagged template literals and optional chains, were not always wrapped in parentheses. The old output was sometimes a syntax error and sometimes changed semantics, for example new (foo()bar)() and new (foo()?.bar)().
  • Hoisted var renaming (#4471). A var declared in a nested scope and hoisted to module scope was not treated as a module-level symbol during the name-collision pass, which could collide names when minification was disabled.
  • ES5 const to var (#4448). For TypeScript-only import x = require('y') constructs targeting ES5, esbuild now emits var instead of const, which it could not downgrade correctly.

Who needs to upgrade

The Deno RCE is the only item that justifies an immediate, everyone-do-it-now upgrade, and only for projects that consume esbuild through Deno in an environment where NPM_CONFIG_REGISTRY is set. Everyone else should still move to 0.28.1 on the next build-tooling bump: the Windows path traversal closes a real (if narrow) exposure, and the using disposal fix is a correctness bug you do not want in minified production output.

The release is also a useful reminder that esbuild is not just a Vite implementation detail. It is the transpilation and minification substrate under a large slice of the Rust-backed JavaScript toolchain, and its two-month release gap is worth noting against the weekly cadence of Oxc and Rolldown. For now, the fixes land cleanly and the upgrade is a bun install esbuild@latest or equivalent. If you run esbuild's dev server on Windows, or use esbuild from Deno behind a custom registry, do it today.

Frequently Asked Questions

Related articles

More coverage with overlapping topics and tags.

Playwright v1.61.0 Lands WebAuthn Passkeys, a Real WebStorage API, and Trace-Style Video Modes for the Test Runner
security

Playwright v1.61.0 Lands WebAuthn Passkeys, a Real WebStorage API, and Trace-Style Video Modes for the Test Runner

Playwright v1.61.0 (June 15, 2026) ships a virtual authenticator for WebAuthn/passkey ceremonies, a first-class page.localStorage / page.sessionStorage API, network security details on API responses, and brings test runner video recording to parity with trace recording. Browser channels: Chromium 149, Firefox 151, WebKit 26.5.
Google's JSIR: An MLIR-Based Intermediate Representation for JavaScript Analysis
security

Google's JSIR: An MLIR-Based Intermediate Representation for JavaScript Analysis

Google has open sourced JSIR, a next-generation JavaScript analysis tool built on MLIR. It supports both high-level dataflow analysis and lossless source-to-source transformation, used internally for Hermes bytecode decompilation and AI-powered JavaScript deobfuscation.
pnpm 11.7 Adds `frozenStore` for Read-Only Filesystems, Lets pacquet Resolve Dependencies, and Closes a Lockfile Path-Traversal
security

pnpm 11.7 Adds `frozenStore` for Read-Only Filesystems, Lets pacquet Resolve Dependencies, and Closes a Lockfile Path-Traversal

pnpm 11.7.0 (June 15, 2026) ships four headline changes: a `--frozen-store` install mode for Nix stores, OCI layers, and other read-only mounts; delegation of dependency resolution to the pacquet Rust port (not just materialization); an opt-in `--batch` flag for `pnpm publish --recursive`; and a security fix that rejects path-traversal and reserved aliases (`.bin`, `.pnpm`, `node_modules`, `../../escape`) in lockfile-sourced dependencies.

Comments

Log in Log in to join the conversation.

No comments yet. Be the first to share your thoughts.