Repository navigation
fix(js): sandbox esbuild's ResolveDir instead of the real filesystem root - #65
Merged
Merged
Conversation
…root
Rendering a production MDN page took ~14.7s wall-clock; pprof showed 88-92%
of total CPU inside esbuild's internal/fs (readdir, symlink resolution) via
resolver.ResolveGlob, called from bundler.parseFile.
Root cause: every OnLoadResult returned by the webengine-http plugin (and
the Stdin entry) declared ResolveDir "/". The plugin fully intercepts
ordinary import resolution, but esbuild's bundler special-cases a *glob*
import — `import(`./${lang}/index.js`)`, ordinary output from a bundler's
locale/route code-splitting, not anything unusual — by resolving it
directly against ResolveDir on the real filesystem via its own internal
resolver, entirely bypassing OnResolve/OnLoad. With ResolveDir "/" and a
pattern whose wildcard has no fixed directory ahead of it (so the walk
starts at ResolveDir itself, not a named subdirectory), a page merely
containing such an import made the engine recursively walk the host's
entire real root filesystem, following symlinks, to find "matches" — for
every module chunk that happened to contain one. Independent of the cost,
the host's real directory tree is not something a remote page should ever
cause this engine to read: this is a sandbox leak, not just a slowdown.
Fix: esbuildSandboxResolveDir() lazily creates one directory (os.MkdirTemp)
that stays empty for the life of the process, and every ResolveDir esbuild
sees now points there instead of "/". A glob import now resolves to zero
matches immediately — a browser's own sandboxed module loader would do the
same — rather than touching the real filesystem at all.
Rendering the same MDN page dropped from ~14.7s to ~0.56s (26x); github.com
(which also ships module scripts) dropped from ~3.3s to ~1.6s. Pages
without module scripts are unaffected.
Two new tests: TestEsbuildSandboxResolveDirIsIsolated asserts the directory
exists, is empty, and is memoized; TestModuleGlobImportDoesNotWalkRealFile-
system is the regression guard — reverting the fix locally reproduces it
failing (it hits the test's 5s bound walking the real filesystem instead
of completing in milliseconds against the sandbox).
tannevaled
added a commit
that referenced
this pull request
Aug 31, 2026
Regenerates results.json/REPORT.md's table and montages against the merged grid track-sizing fix (#63), the esbuild sandbox-escape fix (#65), and the two perf dedupe wins (#64, #66). Records all four in FIDELITY.md's dated log, closes the CSS Grid "Known gaps" bullet (now fixed), and adds an honest note on tailwindcss.com's SSIM number after visually checking the montage.
3 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
OnLoadResult(and theStdinentry) esbuild's module bundler uses declaredResolveDir: "/". This fully intercepts ordinary import resolution via our plugin, but esbuild's bundler special-cases a glob dynamic import (import(\./${lang}/index.js`), ordinary bundler-emitted locale/route code-splitting) by resolving it directly againstResolveDiron the **real filesystem**, entirely bypassingOnResolve/OnLoad`.ResolveDir: "/", a page merely containing such an import made the engine recursively walk the host's real root filesystem (readdir + symlink resolution) looking for "matches" — this is a sandbox leak, not just a slowdown: a remote page should never cause this engine to read the host's real directory tree.esbuildSandboxResolveDir()lazily creates one directory (os.MkdirTemp) that stays empty for the process lifetime; everyResolveDirnow points there, so a glob import resolves to zero matches immediately instead.Impact
developer.mozilla.org/en-US/docs/Web/CSS) dropped from ~14.7s to ~0.56s (26x) — found via pprof, which showed 88-92% of total CPU insideesbuild/internal/fsreaddir/symlink syscalls viaresolver.ResolveGlob.github.com/golang/go(also ships module scripts) dropped from ~3.3s to ~1.6s.Test plan
go test -short ./...— full suite green.TestEsbuildSandboxResolveDirIsIsolated: the sandbox directory exists, is empty, and is memoized across calls.TestModuleGlobImportDoesNotWalkRealFilesystem: regression guard — verified it actually fails (hits the test's 5s bound walking the real filesystem) when the fix is reverted locally, and passes in ~30ms with the fix.cmd/renderbefore/after (14.7s → 0.56s).