On November 4, 2025, someone opened the browser dev tools on Apple's newly redesigned App Store site, noticed a set of .map files loading next to the JavaScript, and clicked "Save All Resources." Minutes later, Apple's entire front-end codebase — Svelte components, TypeScript, routing, the API integration layer — was archived in a public GitHub repo that collected more than 8,000 forks before it came down.
No exploit chain. No zero-day. Just a build that shipped its source maps to production.
If your reaction is "so what, minified JavaScript is already on the client — an attacker could reconstruct it anyway," that reaction is exactly the misconception that keeps this bug alive. A source map does not help someone reconstruct your code. In the common case, it hands them the original, character for character, comments included.
What a .map file actually contains
A source map is a JSON document following the Source Map v3 spec. The interesting fields are small in number:
{
"version": 3,
"file": "app.js",
"sources": ["src/auth/session.ts", "src/api/billing.ts"],
"sourcesContent": [
"// refresh the session before the access token expires\nexport function refreshSession(token: string) { ... }",
"const STRIPE_KEY = process.env.STRIPE_SECRET_KEY; ..."
],
"names": ["refreshSession", "token"],
"mappings": "AAAA,SAAS,cAAT,CAAwB..."
}
mappings is the base64 VLQ encoding everyone pictures when they hear "source map" — the line-and-column bookkeeping that lets a debugger point at the right spot in your original file. That part alone would only de-minify.
The field that leaks is sourcesContent. It embeds the full, unminified text of every original file directly inside the map, keyed one-to-one against sources. Your folder structure, your variable names before mangling, the // TODO: this is a hack, fix before launch comment you forgot about — all of it, verbatim. Bundlers include sourcesContent by default because it makes debugging work even when the original files aren't served, which is precisely why it is so dangerous to ship.
A source map with sourcesContent isn't a map of your code. It is your code, wearing a .json extension.
How someone finds it
The discovery path is boring, which is what makes it reliable. A bundle configured with webpack's devtool: 'source-map' (or the equivalent elsewhere) appends a pointer to the bottom of the emitted JavaScript:
//# sourceMappingURL=app.js.map
A crawler reads that comment and fetches the URL. Even when the comment is stripped — webpack's hidden-source-map, for instance, omits it — the file usually still sits at the predictable path next to the bundle, so app.js becomes a guess of app.js.map. Automated scanners try that guess on every JS asset they see. On the open web, a Google dork like site:example.com filetype:map does the same job.
Reconstruction is a single command. Tools such as unwebpack-sourcemap, reverse-sourcemap, sourcemapper, and shuji read sources and sourcesContent and write the original tree back to disk:
npx reverse-sourcemap -o ./recovered https://example.com/static/js/app.js.map
grep -rniE "api[_-]?key|secret|password|bearer" ./recovered
The second line is the part that turns an information leak into an incident. In one documented case a researcher recovered a production bundle this way and found hardcoded Stripe secret keys, which permitted unauthorized charges. Source maps routinely surface internal endpoints, feature flags, unreleased routes, auth logic, and the occasional credential that "was only ever meant for the build environment."
The scale this reaches
Two weeks before the App Store archive drama had a follow-up: on March 31, 2026, a published npm package shipped a cli.js.map — 59.8 MB, roughly 512,000 lines of TypeScript across about 1,900 files inside sourcesContent. A security researcher spotted it within hours, and the community mirrored the recovered source to GitHub, one repository reaching over 41,500 forks. The root cause was a single missing exclusion in the packaging step; the bundler in use emitted source maps by default unless explicitly told not to. The official line was accurate and also the entire point: not a breach, a packaging error caused by human error. Almost every source map leak is exactly that.
Fixing it without giving up debugging
The instinct is to disable source maps entirely, but you usually want them — readable production stack traces are worth keeping. The correct move is to keep the maps and stop serving them to the public. Two layers do that.
Generate maps, upload them to your error tracker, then delete them before deploy. Sentry, Bugsnag, and similar tools ingest the .map files at build time and resolve stack traces on their side. The map never reaches your web root.
# build → upload → delete, in CI
- run: npm run build # emits app.js + app.js.map
- run: sentry-cli sourcemaps upload ./dist
- run: find ./dist -name "*.map" -delete
If a map must exist on disk, drop the embedded source. Set sourcesContent: false (Vite/Rollup) or use the equivalent so the mappings survive for tooling while the verbatim code does not:
// vite.config.js
export default {
build: {
sourcemap: 'hidden', // no //# comment in the bundle
rollupOptions: { output: { sourcemapExcludeSources: true } },
},
}
And add a backstop at the edge so a stray .map is never reachable:
location ~ \.map$ { return 404; }
Whatever your bundler — tsc, esbuild, Bun, webpack — check its default, because several emit maps unless told otherwise, and "I didn't configure it" is how the file ends up public.
The check to run today
Point curl at your production bundles and see what comes back:
curl -sI https://yourapp.com/static/js/*.js.map | grep -E "HTTP|Content-Length"
A 200 with a fat Content-Length means your repository is a public download right now. A 404 means you're covered. Run it in CI as a release gate so the answer stays 404 on every deploy — the leak isn't a coding mistake you catch in review, it's a build-output default you have to assert against every single time.
Sources: Abusing Exposed Sourcemaps — Sentry, Apple's App Store Source Map Leak — Escape, Anthropic employee error exposes Claude Code source — InfoWorld, Excluding sourcesContent from Source Maps — DEV, JavaScript Source Map Detected — Acunetix