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.
On this page
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:
{
"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
// 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
// vite.config.ts
export default defineConfig({
build: {
sourcemap: false, // or 'hidden' to generate without the public reference
},
})TypeScript Compiler
{
"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:
bun build ./src/index.ts --outdir ./dist --sourcemap=noneThe npm pack --dry-run Habit
Before every publish, run:
npm pack --dry-runThis 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.
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.mdThis 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:
{
"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.
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:
# .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
fiAdd 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:
# 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.mapThe 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:
// Webpack: devtool: false
// Vite: build.sourcemap: false
// Bun: --sourcemap=none
// TSC: "sourceMap": false in tsconfig.build.jsonHow 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:
"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.
Related Articles
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.
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.
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.