Skip to main content
Back to blogs
Security

When a .map File Leaks Your Entire Codebase

Lessons from the Claude Code source map incident — how default build settings can silently ship debug artifacts to production.

April 4, 20266 min read
securityci-cdnpmdevopsbuild-tools

In March 2026, Anthropic accidentally published the entire source code for Claude Code through a .map file included in their npm package. Every file, every comment, every internal constant — all sitting in a JSON file anyone could download. It was the second time it happened in two months.

This wasn't a sophisticated attack. It was a build configuration oversight. And it's a mistake that's far more common than people realize.

What Are Source Maps?

Source maps are files generated by JavaScript bundlers (Webpack, esbuild, Bun, Rollup) that map minified production code back to the original source. They're essential for debugging — when you see an error at bundle.js:1:45892, the source map tells your browser it's actually src/auth/login.ts:47:12.

example.js.map
{
  "version": 3,
  "sources": ["src/auth/login.ts", "src/api/client.ts"],
  "names": ["authenticate", "refreshToken"],
  "mappings": "AAAA,SAAS,IAAI..."
}

The problem: source maps contain enough information to reconstruct your entire original codebase. File paths, function names, variable names, comments, string literals — everything the bundler stripped out to create the minified version.

How This Happens

Most bundlers generate source maps by default. If you don't explicitly disable them for production builds, they ship with your package.

bun-default-behavior.js
// Bun generates source maps by default
// If you don't add this to your build config:
Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  sourcemap: "none",    // THIS LINE prevents the leak
});

The same applies to other bundlers:

webpack.config.js
module.exports = {
  mode: "production",
  // devtool controls source map generation
  // "source-map" generates .map files — DON'T use this in production builds
  // "hidden-source-map" generates maps but doesn't reference them in the bundle
  // false disables source maps entirely
  devtool: false,
};
esbuild-config.js
require("esbuild").buildSync({
  entryPoints: ["src/index.ts"],
  bundle: true,
  minify: true,
  sourcemap: false,     // Explicitly disable for production
  outfile: "dist/bundle.js",
});

The npm-Specific Risk

When publishing to npm, the problem compounds. npm publish includes everything in your project directory unless you explicitly exclude files.

There are two ways to control what gets published:

Allowlist approach (preferred)

package.json
{
  "name": "my-package",
  "files": [
    "dist/**/*.js",
    "dist/**/*.d.ts",
    "!dist/**/*.map"
  ]
}

The files field is an allowlist — only the listed patterns are included. This is safer because new file types are excluded by default.

Blocklist approach

.npmignore
*.map
src/
tests/
.env*
*.config.ts

.npmignore is a blocklist — everything is included unless explicitly excluded. This is riskier because new file types (like .map files from a bundler change) are included by default.

What Gets Exposed

When a source map leaks, an attacker gets:

  • Full source code — every file, reconstructed from the mapping
  • Internal comments// TODO: fix this security check becomes a roadmap
  • Hardcoded strings — API endpoints, internal URLs, feature flag names
  • Architecture details — file structure reveals how the system is organized
  • Potential secrets — any API keys, tokens, or credentials in the source

For AI products specifically, system prompts and orchestration logic get exposed — revealing how the AI is instructed to behave and what guardrails exist.

Preventing This in CI/CD

The fix isn't just "remember to disable source maps." It's building automated checks into your pipeline so a human mistake can't ship debug artifacts.

Step 1: Build Configuration

build.config.ts
const isProduction = process.env.NODE_ENV === "production";
 
export default {
  sourcemap: isProduction ? false : "inline",
  minify: isProduction,
};

Step 2: CI Validation

.github/workflows/publish.yml
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build
 
      - name: Verify no source maps in dist
        run: |
          if find dist -name "*.map" | grep -q .; then
            echo "ERROR: Source map files found in dist/"
            find dist -name "*.map"
            exit 1
          fi
 
      - name: Verify no source maps in package
        run: |
          npm pack --dry-run 2>&1 | grep -i "\.map" && {
            echo "ERROR: .map files would be included in package"
            exit 1
          } || true
 
      - run: npm publish

Step 3: Pre-publish Hook

package.json
{
  "scripts": {
    "prepublishOnly": "node scripts/verify-no-sourcemaps.js"
  }
}
scripts/verify-no-sourcemaps.js
import { globSync } from "fs";
 
const maps = globSync("dist/**/*.map");
if (maps.length > 0) {
  console.error("Source map files found — aborting publish:");
  maps.forEach((f) => console.error(`  ${f}`));
  process.exit(1);
}

The Broader Lesson

This incident isn't really about source maps. It's about a fundamental principle: build tool defaults are not production-safe defaults. Every tool in your build pipeline has settings optimized for developer experience, not for production security.

Audit your build output:

audit-build.sh
#!/bin/bash
echo "=== Files in build output ==="
find dist -type f | sort
 
echo ""
echo "=== Checking for debug artifacts ==="
find dist -name "*.map" -o -name "*.map.js" -o -name "*.d.ts.map"
 
echo ""
echo "=== Checking for source references ==="
grep -r "sourceMappingURL" dist/ || echo "No source map references found"
 
echo ""
echo "=== What npm will publish ==="
npm pack --dry-run 2>&1

Key Takeaways

  1. Source maps reconstruct your entire codebase — they're not just "debug info"
  2. Most bundlers generate them by default — you must explicitly disable them for production
  3. Use files in package.json, not .npmignore — allowlist beats blocklist
  4. Add CI checks for debug artifacts — don't rely on humans remembering build flags
  5. Audit your build output regularly — run npm pack --dry-run and inspect what you're shipping

If a major AI company can ship source maps to npm twice in two months, it can happen to anyone. The difference is whether you have automated guardrails or just good intentions.

Share