Skip to content
🛡️ FREE Master Frontend Security · All 7 modules live · 100% free Start free →

· javascript · 13 min read

Everything you need to know about Sourcemaps

Sourcemaps unlock some observability benefits but might expose your codebase. Check out how they work, and how to protect yourself.

Neciu Dan

Neciu Dan

Hi there, it's Dan, a technical co-founder of an ed-tech startup, host of Señors at Scale - a podcast for Senior Engineers, Organizer of ReactJS Barcelona meetup, international speaker and Staff Software Engineer, I'm here to share insights on combining technology and education to solve real problems.

I write about startup challenges, tech innovations, and the Frontend Development. Subscribe to join me on this journey of transforming education through technology. Want to discuss Tech, Frontend or Startup life? Let's connect.

Share:

I’ve always been curious about sourcemaps.

I knew what they were, but after seeing Charly Gomez’s JNation talk from Sentry, I finally understood them better.

Then, I wrote this article about everything you need to know about sourcemaps, including some of the dangers they introduce.

Enjoy!

What a sourcemap is

When you ship a React app, the browser doesn’t run the code you wrote.

Build tools output minified, bundled code with short variable names and no comments, merging everything into one file.

That’s good for download size, but makes debugging hard. A production error at bundle.min.js:1:48211 tells you absolutely nothing.

A sourcemap is a JSON file that maps minified code back to its original file, line, column, and variable names.

A trimmed-down one looks like this:

{
  "version": 3,
  "file": "bundle.min.js",
  "sources": ["src/UserCard.tsx", "src/api.ts"],
  "sourcesContent": ["function UserCard({ user }) {...", "export async function..."],
  "names": ["UserCard", "user", "fetchUser"],
  "mappings": "AAAA,SAASA,WAAW..."
}

The key fields are sources, sourcesContent, names, and mappings.

sources is the list of original files that went into the bundle, written as paths.

When your build tool reads src/components/billing/InvoiceForm.tsx, that exact path lands here. So when you publish the map, you publish your folder structure and internal module names with it.

names is the list of original identifiers that minification renamed or stripped: function names, variables, properties.

You wrote calculateShippingCost; the bundle shipped c; the string calculateShippingCost lives in this array. When a debugger needs to show you a real name instead of c, it reads it from here.

mappings is the string that ties the two together.

Here, each segment corresponds to a position in the minified output. That segment points back at a file in sources, an exact line and column in it, and a name in names.

When a tool decodes a segment at a given position, it retrieves the original location and identifier for that spot. (The format is base64 VLQ, which keeps the string small)

sourcesContent holds the full text of each original source file, inlined right in the JSON, in the same order as sources.

sources gives you the path, mappings gives you the position, and this gives you the actual code, comments, and all.

How the transformation runs

Now, let’s go through the entire flow to better understand it.

When you write some TypeScript code:

function calculateShippingCost(country: string, weight: number): number {
  const baseRate = 5.0;
  const weightMultiplier = weight * 0.5;
  return baseRate + weightMultiplier;
}

TypeScript compiles it to JavaScript first, stripping the type annotations:

function calculateShippingCost(country, weight) {
  const baseRate = 5.0;
  const weightMultiplier = weight * 0.5;
  return baseRate + weightMultiplier;
}

Then the minifier does its work. It renames every local binding to the shortest legal identifier, drops whitespace, inlines what it can, and collapses everything onto one line:

function c(o,e){return 5+.5*e}

This is what ships to production. Every function is concatenated, creating the unreadable wall in your Network tab.

How the transformation runs in reverse

Open DevTools on a site with sourcemaps, and the browser does the reverse automatically. You set a breakpoint on c, and DevTools shows you calculateShippingCost, with original types and all, because the sourcemap told it how.

When the browser encounters an error in bundle.min.js, it reads the mappings table, decodes the VLQ segments, and finds the one that covers the exact output position. That segment points at a source index, an original line, an original column, and a name index.

The browser grabs the original source and variable name directly from the sourcemap using those indices.

While sourcemaps greatly improve debugging, it’s important to understand the security risks: leaking a sourcemap can expose your entire original codebase to anyone who accesses the map file.

People assume a sourcemap is a thin index, just a table of contents. By default, most modern bundlers embed the full contents of your original source files in the sourcesContent field unless configured otherwise.

Major frameworks often ship with this setting on in development and sometimes forget to disable it in production, especially with templates or generator projects.

If you deploy a sourcemap containing your source code, your readable codebase can be viewed by anyone with access to the .map file. Minification just reduces code size; it does not hide your logic or protect sensitive information.

Sourcemaps serve as keys to decompress your code. If someone obtains the .map file, they can fully reconstruct your source code, eliminating any protection minification provides.

So, who can fetch the .map file

Anyone, if you let them.

Apple’s November 5, 2025, launch of a redesigned web App Store exposed its entire source code, thus revealing how a single configuration mistake can have sweeping consequences.

This happened because Apple shipped to production with sourcemaps enabled, which converts the minified bundle back into the code you wrote. Someone saw this, ran the sourcemaps, and compiled the code using a Chrome Extension that recreated their entire web Apple Store code.

How did that Chrome Extension do this?

Well, the browser (and any reverse sourcemaps tools) finds a sourcemap in one of two ways. Either the bundle ends with a comment pointing at it:

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

Or the server sends a SourceMap HTTP header on the JavaScript response.

Either way, the location is public. DevTools fetches it, and so can curl.

The .map file itself is usually served from the same origin as your scripts, sitting in your static assets directory because your build tool put it there and your deploy step copied the whole directory up.

This security risk does not rely on exotic hacks; it happens to anyone using default build configurations where .map files are publicly accessible due to overlooked settings.

If you use default sourcemap options, upload your dist folder, and don’t confirm .map files are excluded, those files will be public—and so will your unminified source code.

Unless you proactively remove or block .map files, your actual source code can become accessible to anyone with browser access, potentially revealing sensitive logic or secrets. Overlooking this risk can lead to serious security breaches if not addressed carefully.

Quick prevention checklist:

  • Always disable sourcemap generation in production builds.
  • If you must generate sourcemaps for error tracking, never serve them publicly. Upload them only to monitoring tools and keep them out of your public deploy.
  • Exclude .map files from static asset uploads or package managers using .npmignore, a file whitelist, or your deploy scripts.
  • Add a server rule so requests for .map files return 404 or are not accessible.
  • Before shipping, check your deployed site or artifact for .map files. Look in the Network tab or inspect the tarballs in your package.

But “it’s just front-end code.”

The dismissal came immediately after Apple: front-end code runs on the client anyway; the browser already has the minified version, so what did the sourcemap really give away?

A leak reveals your folder structure, module names, comments, API shapes, endpoints, frontend stack, libraries you are using, and feature flags—details not meant to be public.

Sometimes it gives away more than structure. Sourcemaps have leaked API keys and secrets that developers inlined into client code, thinking minification hid them. (Hopefully you don’t do this)

And the people watching for this aren’t curious hobbyists. They’re scraping production bundles of large sites looking for exactly these mistakes, because internal endpoint names and request shapes are the starting map for probing a backend.

Another one

On March 31, 2026, five months on from the Apple incident, Anthropic shipped version 2.1.88 of the @anthropic-ai/claude-code npm package.

Bundled inside was a 59.8 MB cli.js.map file. It mapped roughly 1,900 files and over 512,000 lines of unobfuscated TypeScript, the complete client-side agent harness for Claude Code.

The same mistake struck Anthropic as it did Apple—but here was a difference that made the exposure permanent.

Apple shipped sourcemaps to a site and later removed them after fixing the config. Only those who were quick saw everything (plus they ordered the removal of many GitHub repos via DMCA).

Anthropic’s npm package is instantly downloaded, cached, and mirrored—no config can remove a published version from all caches.

How did it happen? Claude Code’s build runs on Bun, which emits sourcemaps by default, and nobody added *.map to .npmignore or pinned a files allowlist in package.json to keep the artifact out of the tarball.

A security researcher posted the discovery on X. The post pulled tens of millions of views.

Within hours, the codebase was pulled from Anthropic’s own Cloudflare R2 bucket, mirrored to GitHub, and forked tens of thousands of times. A rewritten version of the code reportedly became one of GitHub’s fastest-downloaded repositories ever.

Anthropic’s statement called it a release packaging issue caused by human error, not a security breach, and confirmed no customer data or credentials were exposed.

But the harness itself was the core Claude product.

The leak handed competitors a working blueprint for how a high-agency coding agent is actually built: the API call engine, streaming response handling, the tool-call loop, retry logic, token counting, and the permission model.

Developers combing the code also found feature flags for unshipped features, which means a partial roadmap and some hidden easter eggs and games inside the code.

For a closed-source product shipped as a minified bundle, the sourcemap provided a readable original in a single file.

How to not do this

Every one of these incidents was preventable with a default flip or a one-line server rule.

There are four layers you can implement to protect yourself and your code.

Start at the bundler. Disable sourcemap generation for production builds, or generate them and keep them out of the deployed artifact.

In Vite:

// vite.config.ts
export default defineConfig({
  build: {
    sourcemap: false,
  },
});

In Next.js:

// next.config.js
module.exports = {
  productionBrowserSourceMaps: false,
};

false is already the default in both.

Both Apple and Anthropic had to do something to turn this on. Sourcemaps in production are almost always an opt-in someone forgot to opt back out of, often because they were useful during a debugging session and the flag never came back down.

If you need them for error tracking, hide them.

Generate the .map but keep the browser from loading it. Vite’s sourcemap: 'hidden' builds the file without the comment that points the bundle at it, so DevTools never fetches it.

You upload that file to your monitoring tool and strip it from the public deploy. In Webpack, you can achieve hidden sourcemaps by setting devtool: 'hidden-source-map' in your config.

Most major bundlers have a way to generate sourcemaps without exposing them publicly; always check your framework’s documentation for the right production settings.

Sentry works this way: it takes your sourcemaps at build time and uses them to turn the minified stack traces it receives back into readable ones, with your real file names and line numbers.

Put a backstop on the server. Return a 404 for any .map request so that even if one slips into the deploy, it isn’t reachable:

location ~* \.map$ {
  return 404;
}

Then go check. Open your production site, go to the Network tab, filter for .map. If anything shows up, or if your Sources panel shows readable variable names and comments instead of single letters, your source code is public right now.

If you publish an npm package, the equivalent check is the tarball. Run npm pack, unzip the result, and look.

A .map file in there is the Anthropic mistake waiting to happen, and files in package.json or a .npmignore is what keeps it out.

The uncomfortable takeaway from Apple and Anthropic is that knowing the rule doesn’t save you. Both companies have security teams that know sourcemaps don’t belong in production. The flag got flipped on for a reason that made sense at the time, and nothing automated caught it on the way out.

So the only real protection is the automated check, the thing that fails the build or the deploy when a .map with sourcesContent shows up where the public can reach it.

You do not have to build this from scratch. There are ready-made tools and patterns you can use in your CI/CD pipeline to make sure sourcemaps are never accidentally shipped.

For example, you can add a step to your build process that scans for .map files containing sourcesContent, using a simple shell script like grep -rl 'sourcesContent' dist/*.map or find dist -name '*.map' -exec grep -l 'sourcesContent' {} +.

There are also Node.js scripts and community tools like sourcemap-validator and check-source-maps that can be plugged into your workflow.

Popular CI systems like GitHub Actions or GitLab CI can be configured to fail the pipeline if any .map files are present in the final artifact.

Some static analysis tools and monitoring providers offer plugins that specifically flag public sourcemaps during deployment.

Adding even a basic script to your predeploy or postbuild step to block unwanted sourcemaps goes a long way toward making this catch repeatable and impossible to forget.

Here is an example of a script scripts/check-sourcemaps.mjs

// scripts/check-sourcemaps.mjs
// Fails the build if any .map in the output directory inlines original
// source via `sourcesContent`. A map without it leaks paths but not code,
// so we let those pass; set ALLOW_EMPTY_MAPS to false to block all maps.

import { readFileSync } from "node:fs";
import { glob } from "node:fs/promises";

const OUTPUT_DIR = process.argv[2] ?? "dist";
const ALLOW_EMPTY_MAPS = true;

const offenders = [];

for await (const file of glob(`${OUTPUT_DIR}/**/*.map`)) {
  let map;
  try {
    map = JSON.parse(readFileSync(file, "utf8"));
  } catch {
    offenders.push({ file, reason: "unparseable .map file" });
    continue;
  }

  const hasInlinedSource =
    Array.isArray(map.sourcesContent) &&
    map.sourcesContent.some((c) => typeof c === "string" && c.length > 0);

  if (hasInlinedSource) {
    offenders.push({ file, reason: "contains sourcesContent (original code)" });
  } else if (!ALLOW_EMPTY_MAPS) {
    offenders.push({ file, reason: "source map present" });
  }
}

if (offenders.length > 0) {
  console.error(`\n✖ Source map check failed in "${OUTPUT_DIR}":\n`);
  for (const { file, reason } of offenders) {
    console.error(`  ${file}\n    -> ${reason}`);
  }
  console.error(
    `\n${offenders.length} file(s) would ship your source. ` +
      `Disable production source maps or strip them before deploy.\n`
  );
  process.exit(1);
}

console.log(`✓ No source-leaking maps found in "${OUTPUT_DIR}".`);

Wire it into package.json so it runs after every build:

{
  "scripts": {
    "build": "vite build",
    "postbuild": "node scripts/check-sourcemaps.mjs dist"
  }
}

And the GitHub Actions step:

# .github/workflows/deploy.yml
name: build
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
      - run: npm ci
      - run: npm run build        # postbuild runs the check automatically
      # Or call it as its own step if you prefer it explicit:
      # - run: node scripts/check-sourcemaps.mjs dist

Three things worth knowing before you ship it:

  • node:fs/promises glob needs Node 22+. On older runners, swap it for the glob npm package (import { glob } from “glob”) or a find dist -name ‘*.map’ shell step.
  • The default allows maps with no sourcesContent because the safe error-tracking setup (build with hidden maps, upload them to Sentry, strip from deploy) produces exactly those. If your policy is “no .map reaches the artifact at all,” switch ALLOW_EMPTY_MAPS to false.
  • Point it at the right directory. Next.js outputs to .next (and serves browser maps under _next/static/), so you’d call it with .next rather than dist.

Thats it! Automate the check once, and it catches every slip after that.

References

🛡️ FRONTEND SECURITY · REACT · VUE · ANGULAR · VANILLA JS

Master Security in Frontend Applications

Free, comprehensive frontend security course.
XSS, CSRF, AI security, broken access control & the vulnerabilities that actually get you breached.

100% FREE 7 MODULES · ALL LIVE
Start learning free →

All 7 modules live now. No credit card.

Neciu Dan

Discover more from The Neciu Dan Newsletter

A weekly column on Tech & Education, startup building and occasional hot takes.

Over 1,000 subscribers

🎙️ Latest Podcast Episodes

Dive deeper with conversations from senior engineers about scaling applications, teams, and careers.

Monorepos at Scale with Santosh Yadav
Episode 40
58 minutes

Señors @ Scale host Neciu Dan sits down with Santosh Yadav, principal developer advocate at CodeRabbit and one of only around 80 GitHub Stars in the world. Santosh started hating C in 2004, fell for C# by 2008, and turned a year of open source contributions to Angular and NgRx into a stack of community titles — Google Developer Expert, GitHub Star, Nx champion, and Microsoft MVP. As a staff engineer at Celonis he led the move of 20-plus apps to module federation and drove Nx adoption across 30-plus teams when the product grew from four apps to thirty. From the year-long incremental migration off a single deployable unit, to why polyrepos can't give AI tools the context they need, to how Nx's affected graph and build caching tame a 20-million-line monorepo, to running code review for free for open source at CodeRabbit, this is the monorepo conversation grounded in someone who actually shipped one at scale.

📖 Read Takeaways
Routing at Scale with Nicolas Beaussart-Hatchuel
Episode 39
54 minutes

Señors @ Scale host Dan Neciu sits down with Nicolas Beaussart-Hatchuel, staff engineer at Payfit and one of the maintainers of TanStack Router. Nicolas's path started with C macros to auto-generate his student paper headers and frontend learned by building phishing login pages for practice, took him through an iframe-based AngularJS-to-Angular 2 micro frontend migration at a web radio platform, into open source contributions across NX, ESLint, Vite and Hasura, and finally to maintaining one of the most ambitious routers in the React ecosystem. From why TanStack Router exists, to migrating Payfit's 300-route, 1.5-million-line codebase off React Router v5 using the strangler pattern, to collapsing 25 polyrepos and five different micro frontend strategies into a single modular monolith, this is the routing conversation most engineers never get.

📖 Read Takeaways
Redux at Scale with Mark Erikson
Episode 38
57 minutes

Señors @ Scale host Neciu Dan sits down with Mark Erikson, maintainer of Redux and senior front-end engineer at Replay.io, where he works on a time-traveling debugger. Mark's path started with a 286 he got at eight years old, ran through a computer science degree, four years teaching English in China, embedded software at Northrop Grumman emulating legacy CPUs in old aircraft, and a chain of projects — GWT, jQuery, Backbone — that led him to React and Redux. From the @deprecated backlash that had people insulting him on the internet, to why the Redux core hasn't meaningfully changed since 2016, to what RTK Query actually solves, the underused listener middleware, building source maps into React's own build pipeline, and how Replay's recordings now hand debugging over to AI agents — this is the Redux conversation grounded in two decades of shipping software.

📖 Read Takeaways
TanStack Query at Scale with Dominik Dorfmeister
Episode 37
53 minutes

Señors @ Scale host Dan Neciu sits down with Dominik Dorfmeister — better known as TkDodo — the maintainer of TanStack Query and a software engineer at Sentry. Dominik's path started at a technical high school in Vienna, ran through JVM backend work in Java and Scala, and turned to frontend around the introduction of TypeScript. During the pandemic lockdowns in Austria he started answering questions in the TanStack Discord, got addicted to the instant gratification of helping people, and slowly turned that into a blog, a first code contribution six to eight months later, and eventually maintainership of TanStack Query. From tracked queries and the chaotic version-three-to-four rename, to the version-five mistake he still dreads, to ripping 28,000 lines of dead code out of Sentry with Knip and building Sentry's new design system, this is the open source maintenance conversation most developers never get to hear.

📖 Read Takeaways
Back to Blog