ibit
Notes

Shipping sourcemaps with sourcesContent leaks your whole source

WRWhite Rabbit·June 29, 2026·3 min

Open the network tab on almost any production site, find a .js bundle, and append .map to its URL. If the file loads, you can usually read the site's original source — the unminified version, with variable names, folder structure, and comments intact. Not the gnarly one-line bundle. The real thing.

That gap surprised me the first time I really thought about it, so I went and read this deep dive on sourcemaps to understand the mechanism. The short version: people picture a sourcemap as a cryptic lookup table, and one part of it is. The mappings field is an opaque string of base64 VLQ-encoded position deltas — line and column offsets pointing from the minified output back into the originals. You'd need tooling to make sense of it.

But mappings isn't the field that leaks. The one that leaks is sourcesContent: a plain array that inlines the full text of each original file, verbatim. No decoding required. Open the JSON, read the strings. That's your code.

Here's the part worth internalizing: minification was never obfuscation, and the .map makes that explicit. Once sourcesContent is public, the minification step buys you nothing as protection. Folder layout, internal endpoint paths, feature-flag names, the TODO comment you left in a payments helper — all trivially reconstructable, as the article walks through. You shipped a .zip of your repo next to the bundle and called it minified.

The reflex is "stop generating sourcemaps." Wrong fix. You want maps — for readable stack traces in your error tracker, for debugging prod. The right mental model is about where the maps live: generate them, upload them privately to something like Sentry, and keep them off the public deploy. In Vite that's build.sourcemap: 'hidden' — it still emits the .map (so you can upload it) but drops the //# sourceMappingURL comment that advertises it to browsers. Webpack's hidden-source-map does the same. If you don't need the originals at all, Rollup's output.sourcemapExcludeSources empties sourcesContent while keeping the position mappings.

But "hidden" still writes the file to disk, and the comment-stripping is easy to undo by accident. So the fix I actually trust is dumber: a CI step that scans every emitted .map and fails the build if any has a populated sourcesContent. Human review of build output reliably loses to a forgotten default — this isn't a junior mistake, it's the kind of thing that survives precisely because nobody looks at the out/ folder. A check that fails loudly beats a convention nobody remembers.

This site sharpens the choice. ibit is a zero-server static export — there is no private place to stash maps behind. Every byte in out/ ships to the browser. So the decision isn't "where do the maps go," it's binary: omit sourcesContent from the production build, or accept that the source is public. For a personal blog that's fine. For anything with secrets in the source, it's the whole ballgame.