The secrets hiding in your JavaScript

What are source maps, and are you handing strangers your original code?

Open your production JavaScript and it looks like a wall. Minified, mangled, single-line gibberish. Variable names are single letters, your structure is gone, nobody could read this. So you stop worrying about what's in it.

But did that same build also ship the source map that turns all of it back into your original code? One file sitting next to app.min.js can undo every bit of that minification.

A source map is a file your bundler writes, usually named like app.min.js.map, that maps every minified character back to the original source line it came from. It exists so you can debug minified code in devtools and read a real stack trace instead of a calling b at column 4,812. Useful in development. Served publicly in production, it's a decompiler, and minified means nothing once it ships next to your code.

What the map actually contains

Open a source map and it's JSON. The interesting field is sourcesContent: an array holding the full, original text of every source file that went into the bundle. Not a reconstruction. The actual files. Your real variable names, your comments, your folder structure under src/, your component logic, the TODO you left in, all of it.

Browsers find these maps automatically. The bundler appends a comment to the bottom of the JavaScript file, //# sourceMappingURL=app.min.js.map, and devtools follows it. So does anyone who reads that comment and fetches the URL by hand.

request
GET /assets/app.min.js.map HTTP/1.1 Host: yoursite.com
response
HTTP/1.1 200 OK
content-type: application/json
{"version":3,"sources":["src/lib/auth.ts",
"src/lib/billing.ts","src/config.ts"],
"sourcesContent":["const SIGNING_SECRET =
\"whsec_3aF...\"; export function verify...
One GET request. The map hands back your original source, comments and all.

How they get shipped by accident

Most bundlers generate source maps for production builds by default or with one flag, and the default behavior is what bites people. Older create-react-app builds wrote maps into the deploy folder unless you set GENERATE_SOURCEMAP=false. Next.js can emit client source maps with productionBrowserSourceMaps: true, and plenty of people turn it on to debug a production issue and never turn it off. Vite writes maps when build.sourcemap is true. Then your static host serves whatever is in the output folder, maps included, no questions asked.

The trap is that everything works. The site loads, the minified JavaScript runs, nobody notices the extra .map files sitting next to it. They're only ever requested by devtools or by someone looking for them, so they're invisible in normal use. You can have been serving your full source for a year and have no signal that you are.

What a stranger does with your source

Reading your minified bundle is slow and annoying. Reading your original source is not. A leaked source map collapses the work an attacker has to do, and the first thing they read it for is secrets.

This is where source maps overlap with every other article in this section. The same hardcoded credentials that hide in a minified bundle become trivially findable in the source map, because now they sit in named files with comments explaining them. A sk_live_ here, a service_role JWT there, an API token in a config file labeled "internal only." The map exposes the code and everything the code was hiding in plain sight.

Past the secrets, they get your architecture for free: which endpoints you call, how your auth flow is wired, where your feature flags live, the names of internal services. That's reconnaissance you'd normally make them work for, handed over in one download.

Check it, then stop serving it

You can confirm this in a minute.

  1. Open your site and open developer tools (F12).
  2. In the Sources tab, look for a folder tree with your original file names (src/...). If you can browse your real source, the maps are being served.
  3. Or just fetch one directly: find a //# sourceMappingURL= comment at the end of a .js file, then request that .map URL in the browser. A 200 with JSON is a leak.

The fix is to stop emitting maps to the public build, or stop serving them. Best is to keep generating them in CI and upload them privately to your error tracker (Sentry and similar accept maps and use them to symbolicate stack traces) while deleting them from the deployed assets. Failing that, turn off public source map generation entirely.

// next.config.js
module.exports = {
productionBrowserSourceMaps: true, // serves app.min.js.map publicly
};

// or vite.config.ts
export default { build: { sourcemap: true } };
Generate maps for your error tracker, not for the public. The browser never needs the .map file.

If you only do one thing, fetch one of your own .map URLs right now and see whether your source comes back. The map either returns your code or it doesn't, and that answer takes one request.

That request is exactly what SurfaceCheckr makes for you. We fetch your bundles, follow the sourceMappingURL comment, and check whether the .map answers with your source, the same 200 a stranger would get. It happens from the outside, so we never see your build pipeline or your backend, only what's already downloadable. That's narrower than a pentest by design, and on this finding it's also all you need: scan your domain and we'll tell you if your original code is sitting on the public internet.

Find it before someone else does.

Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.