Dev Encyclopedia
ArticlesTools

Get notified when new content drops

No spam. Just new articles, tools, and updates straight to your inbox.

Dev Encyclopedia

A reference for builders

Content

  • Articles
  • Tools
  • Contact

Connect

  • support@devencyclopedia.com
  • RSS Feed

© 2026 Dev Encyclopedia

Privacy PolicyTermsDisclaimer
  1. Home
  2. /Blog
  3. /Source Maps Are Leaking Your Code: How to Stop It in Your npm Packages
security7 min read

Source Maps Are Leaking Your Code: How to Stop It in Your npm Packages

Source maps with sourcesContent can leak your entire codebase. Learn how to disable them per bundler, audit with npm pack, and automate the check in CI.

By Dev EncyclopediaPublished June 21, 2026
On this page

On this page

  • What's Actually in the File
  • Disabling Source Maps Per Bundler
  • Webpack
  • Vite / Rollup
  • TypeScript Compiler
  • Bun
  • The npm pack --dry-run Habit
  • Explicit Allowlisting in package.json
  • Automating the Check in CI
  • If You Need Source Maps for Error Tracking
  • Frequently Asked Questions

A source map is a small JSON file that maps your minified, bundled production JavaScript back to the original source code you actually wrote. They exist so that when an error happens in production, your error tracker can show you a readable stack trace instead of a pointer into a single 2 MB line of minified code.

The problem is one specific field inside that JSON: sourcesContent. When populated, it contains the complete original source files, every line, every comment, in plain text, embedded directly inside the map. If that map file ends up somewhere public, so does your entire original codebase.

This is not a hypothetical. A widely covered packaging mistake earlier this year made this exact failure mode the talk of the JavaScript ecosystem for a week: a published npm package included a source map with sourcesContent populated, and within hours the original TypeScript source had been fully reconstructed and mirrored publicly. The mechanism was identical to dozens of smaller, less-publicized incidents that happen regularly.

Here is how to make sure it does not happen to your project.

What's Actually in the File

A source map looks roughly like this:

json — dist/index.js.map
{
  "version": 3,
  "sources": ["../src/utils/auth.ts"],
  "sourcesContent": [
    "export function validateToken(token: string) {\n  // full original source here...\n}"
  ],
  "mappings": "AAAA,SAASA..."
}

The sources array lists original file paths. The sourcesContent array, when present, contains the full text of each of those files. That second array is the part that turns a debugging convenience into a code disclosure risk.

If a .map file with sourcesContent populated ends up publicly accessible (published to npm, deployed to a public web server, left in a public CDN bucket) anyone who finds it can reconstruct the entire original codebase. No exploit required, just a downloaded file.

Disabling Source Maps Per Bundler

The fix depends on your build tool, but the principle is the same everywhere: do not generate sourcesContent in artifacts that leave your build environment.

Webpack

javascript — webpack.prod.js
// webpack.prod.js
module.exports = {
  mode: 'production',
  devtool: false, // No source maps at all
  // OR, if you need maps for error tracking but not public exposure:
  // devtool: 'hidden-source-map',
}

Setting devtool: false is the safest option. If you need source maps for error tracking (covered below), use hidden-source-map instead. It generates the file but omits the //# sourceMappingURL comment that tells browsers where to find it.

Vite / Rollup

typescript — vite.config.ts
// vite.config.ts
export default defineConfig({
  build: {
    sourcemap: false, // or 'hidden' to generate without the public reference
  },
})

TypeScript Compiler

json — tsconfig.build.json
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "sourceMap": false,
    "declarationMap": false
  }
}

Keep source maps enabled in your development config; disable them only in the production build config. Using two separate tsconfig files for dev vs build keeps this explicit rather than relying on an environment variable check that is easy to forget.

Bun

Bun generates source maps by default. This is the detail that catches people off guard, since most bundlers default to maps-off in production mode. Pin the flag explicitly rather than relying on documented default behavior:

bash
bun build ./src/index.ts --outdir ./dist --sourcemap=none

⚠ Bun defaults to source maps on

Unlike Webpack and Vite, Bun generates source maps by default. If you use Bun as your bundler, you must explicitly pass --sourcemap=none for production builds.

The npm pack --dry-run Habit

Before every publish, run:

bash
npm pack --dry-run

This prints exactly what would be included in the published tarball, without actually publishing anything. Read the file list. If you see any .map files in there, stop and fix your config before publishing.

bash
npm pack --dry-run
npm notice
npm notice 📦  your-package@2.1.0
npm notice === Tarball Contents ===
npm notice 1.2kB  package.json
npm notice 4.5kB  dist/index.js
npm notice 89.3kB dist/index.js.map  ← this should not be here
npm notice 2.1kB  README.md

This one command, run as a habit before every release, would have caught the high-profile incident mentioned earlier before it ever reached the registry.

Explicit Allowlisting in package.json

Relying on .npmignore to exclude .map files is a blocklist approach: it fails open. If someone adds a new build step that generates files in an unexpected location, .npmignore will not know to exclude them unless someone remembers to update it.

An explicit files field in package.json is an allowlist: it fails safe. Only what is explicitly listed gets published, regardless of what else exists in your working directory:

json — package.json
{
  "name": "your-package",
  "files": [
    "dist/**/*.js",
    "dist/**/*.d.ts",
    "README.md",
    "LICENSE"
  ]
}

Notice what is absent from that list: *.map. Anything not explicitly included simply does not ship, no matter what your build process generates. Check your own package.json against these risks instantly with SourceMapCheck.

💡 Allowlists fail safe, blocklists fail open

The files field in package.json is strictly better than .npmignore for security. With an allowlist, new files are excluded by default. With a blocklist, new files are included by default.

Automating the Check in CI

Manual habits get forgotten under deadline pressure. The reliable fix lives in CI, not in memory. Add this as a CI gate using the same workflow pattern you use for other security checks:

yaml — .github/workflows/publish.yml
# .github/workflows/publish.yml
- name: Audit package contents before publish
  run: |
    npm pack --dry-run > pack-output.txt 2>&1
    if grep -q '\.map' pack-output.txt; then
      echo "::error::Source map file detected in package contents."
      exit 1
    fi

Add this as a required step before any publish job. It fails the build automatically if a .map file would be included, so no human has to remember to check.

If You Need Source Maps for Error Tracking

There is a legitimate reason to generate source maps in production: readable stack traces in your error tracking tool. The safe pattern is to generate them, upload them privately to your error tracker during the build, then delete them before the final artifact ships:

bash
# Build with source maps
npm run build  # generates dist/*.js and dist/*.js.map

# Upload maps to your error tracker (example: Sentry CLI)
sentry-cli sourcemaps upload ./dist

# Delete the maps before deploying
rm dist/*.js.map

The map files exist briefly during the build pipeline, get uploaded to a private destination, and never appear in the artifact that actually ships. Your error tracker can still de-minify stack traces using its private copy; nothing public-facing ever has access to your original source.

DepScan audits what you are installing; this guide covers what you might be accidentally publishing. The same build-hygiene discipline that Bumblebee enforces at the machine level applies to your own published packages.

Frequently Asked Questions

What is sourcesContent in a source map?

sourcesContent is an optional array in the source map JSON spec (version 3). Each entry contains the full original text of a source file referenced by the sources array.

  • Why it exists — lets debuggers show original source without needing access to the source files on disk
  • Why it is risky — embeds your complete, unminified codebase in a single downloadable JSON file
  • When it is populated — most bundlers include it by default unless you explicitly disable it
How do I disable source maps in production?

The config depends on your bundler:

javascript
// Webpack: devtool: false
// Vite:    build.sourcemap: false
// Bun:     --sourcemap=none
// TSC:     "sourceMap": false in tsconfig.build.json

💡 Tip

Use a separate production config (like tsconfig.build.json) so development still gets source maps for local debugging.

How do I stop npm from publishing .map files?

Add an explicit files field to your package.json that only lists the file types you intend to ship:

json
"files": ["dist/**/*.js", "dist/**/*.d.ts"]

This is an allowlist approach. Since *.map is not in the list, source maps are excluded regardless of where they are generated. Verify by running npm pack --dry-run before every publish.

Can source maps leak API keys?

Yes, if your original source code contains hardcoded secrets (API keys, tokens, connection strings), those strings appear verbatim in sourcesContent. Even if the secrets are stripped or replaced during the build, the source map preserves the pre-build version.

🚫 Danger

Source maps preserve the pre-build source. Environment variable substitution in the bundled output does not affect what appears in sourcesContent. Never hardcode secrets in source files.

What is the difference between hidden-source-map and source-map in Webpack?
Featuresource-maphidden-source-map
Generates .map fileYesYes
Adds //# sourceMappingURL commentYesNo
Browsers auto-load the mapYesNo
Best forDevelopmentError tracking (Sentry, Datadog)

Use hidden-source-map when you need to upload source maps to an error tracking service but do not want browsers to discover and load them. The map file still exists on disk after the build, so you must delete it (or exclude it from your deploy) after uploading.

Related Articles

security

GitHub Actions Security: 7 Misconfigurations to Avoid

The 7 GitHub Actions misconfigurations behind real supply chain attacks: weak GITHUB_TOKEN scope, pull_request_target, unpinned actions, script injection.

Jun 12, 2026·14 min read
security

Bumblebee Tutorial: Scan Your Dev Machine for Supply Chain Risks

How to install and use Bumblebee, Perplexity's open-source scanner for npm, MCP configs, and extensions. Real commands, scan profiles, and incident response setup.

Jun 21, 2026·11 min read
security

WordPress CDN Supply Chain Attack 2026: What Happened and How to Check Your Site

The OptinMonster, TrustPulse, and PushEngage supply chain attack (June 2026) hit 1.2M sites. Here's exactly how it worked, how to check if you were compromised, and how to recover.

Jun 21, 2026·12 min read

On this page

  • What's Actually in the File
  • Disabling Source Maps Per Bundler
  • Webpack
  • Vite / Rollup
  • TypeScript Compiler
  • Bun
  • The npm pack --dry-run Habit
  • Explicit Allowlisting in package.json
  • Automating the Check in CI
  • If You Need Source Maps for Error Tracking
  • Frequently Asked Questions
Advertisement