Skip to main content

Source Maps in Production: How They Leak Your Code

Updated 6 min readMarkdown

You minified your bundle, maybe even obfuscated it, and shipped. Then someone opens DevTools and sees your original files: folder structure, variable names, comments, TODOs and all. The culprit is almost always a public source map. This guide explains how the leak works, how to check whether you are affected, and how to keep the debugging benefits without publishing your code.

What a source map contains

A source map is a JSON file (usually app.js.map) that maps positions in your built output back to your original files. Browsers load it when DevTools is open, so stack traces and breakpoints show real code.

The important field is sourcesContent:

{
  "version": 3,
  "file": "app.min.js",
  "sources": ["../src/billing/license.ts", "../src/api/client.ts"],
  "sourcesContent": ["// TODO: remove hardcoded test key\nexport function checkLicense(...", "..."],
  "mappings": "AAAA,SAASA,..."
}

If sourcesContent is present, the map holds a full copy of your original source, including comments. Even without it, sources and names reveal your file layout and original identifiers.

How the leak happens

The built file ends with a comment like:

//# sourceMappingURL=app.min.js.map

If that .map file is deployed to the same public directory, anyone can download it. Tools exist that take a site URL, fetch every map and rebuild the original project tree in seconds. No reverse engineering skill is needed.

Common causes:

  • devtool: "source-map" left on in a webpack production config
  • build.sourcemap: true in Vite, copied from a tutorial
  • framework defaults that changed between versions
  • CI uploading the whole dist/ folder, maps included
  • a CDN or error-monitoring setup that needed maps and published them publicly

Source maps also undo obfuscation. If you obfuscate and then generate a map that points back to the original, you have shipped the key to your own lock.

How to check your site

  1. Open your site, then DevTools → Sources. If you see a src/ or webpack:// tree with readable files, maps are loading.
  2. Look at the last line of each production JS file for sourceMappingURL.
  3. Request the map directly:
curl -sI https://example.com/assets/index-abc123.js.map
# HTTP 200 means it is public
  1. Check for the SourceMap or X-SourceMap response header on JS files, which also points to maps.
  2. Search old deployments too. Hashed files from previous builds often stay on CDNs for months.

Fixing it by tool

Vite

// vite.config.js
export default {
  build: {
    sourcemap: false        // no maps at all
    // or "hidden": generate maps but omit the sourceMappingURL comment
  }
};

"hidden" is useful if you upload maps to an error tracker. You still need to make sure the .map files are not deployed publicly.

webpack

module.exports = {
  mode: "production",
  devtool: false               // or "hidden-source-map"
};

Avoid eval-* and inline-* variants in production: inline maps embed the full source inside the JS file itself.

Next.js

productionBrowserSourceMaps is off by default. If you turned it on, turn it off, or upload maps to your monitoring tool and delete them from the output before deploying.

esbuild and Rollup

esbuild: omit --sourcemap, or use --sourcemap=external and keep the files out of the deploy. Rollup: set output.sourcemap to false or "hidden".

Keep debugging without leaking

You can have readable production stack traces without publishing source:

  • Hidden maps + private upload. Generate hidden maps, upload them to Sentry or a similar service during CI, then delete them before deploy.
  • Exclude sources content. Some tools allow maps without sourcesContent. This still reveals names and paths, so prefer private storage.
  • Restrict access. Serve .map files only from an internal host or behind authentication if your team needs them in the browser.

A simple CI guard catches regressions:

if find dist -name "*.map" | grep -q .; then
  echo "Source maps found in dist/, refusing to deploy"; exit 1
fi

Already leaked? What to do

  1. Remove the maps from your origin and purge the CDN cache, including old hashed files.
  2. Assume the source was copied. Review it for secrets, internal URLs and admin endpoints.
  3. Rotate any credential that appeared in the source, even in a comment.
  4. Check that server-side authorization does not rely on obscurity of endpoint names.
  5. If valuable logic was exposed, consider moving it to the server or protecting it in future builds.

Source maps and code protection

Removing maps is the first step of any JavaScript protection plan; there is no point obfuscating code you also publish in its original form. After that, minification removes comments and names, standard obfuscation hides structure, and VM-based protection compiles critical functions into custom bytecode so they cannot be mapped back to readable JS. That raises the cost of reverse engineering a lot, though nothing that runs on a user's device is unbreakable. See how to protect JavaScript source code for the full layered approach and how attackers deobfuscate JavaScript for what you are defending against.

FAQ

Should I disable source maps in production?

Disable public source maps, yes. You can still generate hidden maps and upload them privately to an error-monitoring service so stack traces stay readable for your team.

Can someone recover my code from a source map?

Yes. If the map includes sourcesContent, it contains your original files verbatim, and free tools can rebuild the whole project tree from a site URL.

Do source maps affect performance or SEO?

Browsers only download maps when DevTools is open, so normal users are not slowed down. They have no SEO benefit. The cost is purely the information they expose.

Protect what is left

Once maps are gone, the shipped code is still readable after formatting. If you want to see how VM protection treats a sensitive function, VEILVM has a browser trial with no sign-up: 3 runs a day, files up to 5 KB (resets at 00:00 UTC). Standard requests are processed in memory and your code is never written to logs. Try it at https://vm.ldvmp.com.

No comments yet. Be the first to comment.