---
title: "Everything you need to know about Sourcemaps"
publishDate: 2026-06-01T00:00:00.000Z
excerpt: "Sourcemaps unlock some observability benefits but might expose your codebase. Check out how they work, and how to protect yourself."
category: "javascript"
tags: ["javascript", "build", "ci-cd", "vite", "security"]
canonical: https://neciudan.dev/everything-you-need-to-know-about-sourcemaps
---

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:

```json
{
  "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:

```tsx
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:

```js
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:

```js
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:

```js
//# 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:

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

In Next.js:

```ts
// 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. 

For Next.js, while public browser sourcemaps are disabled by default in production, you can generate server-side only sourcemaps by setting `productionBrowserSourceMaps: false` and uploading the generated `.map` files directly to your monitoring provider. 

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:

```nginx
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

- [Apple accidentally leaks new web App Store front-end source code](https://9to5mac.com/2025/11/04/web-app-store-front-end-source-code-github/) (9to5Mac)
- [Apple's App Store Source Map Leak: A Preventable Vulnerability](https://escape.tech/blog/apple-app-store-source-map-leak/) (Escape)
- [Claude Code's source code appears to have leaked](https://venturebeat.com/technology/claude-codes-source-code-appears-to-have-leaked-heres-what-we-know) (VentureBeat)
- [Anthropic accidentally exposes Claude Code source code](https://www.theregister.com/2026/03/31/anthropic_claude_code_source_code/) (The Register)
- [Source Map Revision 3 Proposal](https://sourcemaps.info/spec.html) (the sourcemap spec)
- [MDN: Use a source map](https://developer.mozilla.org/en-US/docs/Web/API/Web_Performance_API/Source_map)
