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()orrequire()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. newoperator wrapping (#4477). Complex targets ofnew, 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 examplenew (foo()bar)()andnew (foo()?.bar)().- Hoisted
varrenaming (#4471). Avardeclared 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
consttovar(#4448). For TypeScript-onlyimport x = require('y')constructs targeting ES5, esbuild now emitsvarinstead ofconst, 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.



