Source Map Leak Post-Mortem and Pre-Publish Checks
This article reviews a common source map leak incident in which a public package accidentally includes a `.js.map` file, explaining how source maps can expose original code, why release pipelines leak them, and what pre-publish checks can prevent the issue. It also covers risk assessment, allowlists, tarball scanning, secret detection, and emergency response after a leak.
1. Incident Review: A File That Should Not Have Been Published
This article is a consolidated post-mortem of a class of incidents that the industry has disclosed several times in recent years. The details have been abstracted and merged, and this article does not target any specific product. The common scenario is as follows: during a version update, a tool’s build pipeline mistakenly includes a large source map file, typically named *.js.map, in an externally distributed installation package. Once pushed to a public package registry, anyone can download it. With that file, outside parties can “decompile” the compressed and obfuscated output, quickly reconstruct much of the project’s source file structure, and may even uncover internal feature modules, experimental capabilities, or internal toolchain information that was never officially released.
It is worth noting that such incidents are usually not hacker intrusions. They are a typical configuration leak: there is no vulnerability exploit and no privilege escalation, only a build artifact that should not have left the release process being pushed out as a normal file. The issue exposes systemic blind spots in build and release processes, not a single code defect.
If you are auditing your own project, three common questions usually arise: Does a source map leak source code? How can you check whether a .js.map file is hidden in a release package? How can you prevent source map leakage at release time? The following sections address these questions one by one.
2. What Is a Source Map, and Why Can It “Reconstruct” Source Code?
Modern frontend and Node.js projects almost always go through a “compression plus transpilation” process: TypeScript is compiled to JavaScript, code is minified, variable names are obfuscated, and multiple modules are bundled into a single file. These steps can reduce size, improve loading speed, and, to some extent, make the code harder to read.
However, to let browsers, Node.js debuggers, or error monitoring platforms locate the original error position, the toolchain also generates a source map. It is a JSON structure whose core purpose is to record which original file, line, and column each segment of the transformed output corresponds to. A simplified example is as follows:
{"version": 3,"file": "cli.js","sources": ["../src/engine/QueryEngine.ts", "../src/tools/index.ts"],"sourcesContent": ["export class QueryEngine {\n async run(query) { ... }","export const tools = [ ... ]"],"names": [],"mappings": "AAAA,SAAS,GAAG,..."}
The key field is sourcesContent. It can inline the complete original source code directly into the map file. In other words, once the .map file is publicly released, external parties do not need sophisticated reverse-engineering techniques. If they load the file into a debugger that supports source maps, the compressed output can potentially be restored into a source tree with complete file names, variable names, and some comments.
However, it must be emphasized: whether source code is leaked depends on whether the map file inlines source content. If sourcesContent is empty, redacted, or if the build tool explicitly disables source content output, the source map mainly provides position mapping rather than the source text itself. The level of risk depends on whether the map file actually carries readable source code.
Configuration directions for reducing risk include:
- webpack:
nosources-source-map. Generate the map file without inlining source content, keeping only position mapping. This can be used to locate errors, but it does not directly bring source text into the map file. - esbuild:
sourcesContent: false. Disable source content inlining so that the.mapfile contains only the necessary position information. - TypeScript / tsc: If the published output does not need source maps, set
sourceMap: false. If maps must be generated for troubleshooting, be careful not to accidentally enable options such asinlineSourcesthat may bring source content into the output, and strip map files before release. hidden-source-map: Generate a.mapfile but do not write a//# sourceMappingURLcomment at the end of the output. Browsers will not automatically load it by default. However, this does not meansourcesContentis automatically removed.- Direct maps to an authenticated private platform: Upload map files from build output only to an error tracking service that requires login, and never publish them in the package.
3. Root Cause: Unclear Boundary Between Build Artifacts and Release Artifacts
Why would such an apparently simple mistake happen in teams with strong technical capabilities? The root cause is that “build artifacts” and “release artifacts” are often two different things, and the boundary between them is not strictly controlled.
Common causes include:
- Source maps are enabled during build but not removed during release. Teams often generate source maps during build and upload them to a private error tracking service for online troubleshooting and error monitoring aggregation. However, the copy published to a public registry should use a separate configuration that strips source maps.
- Missing artifact allowlist. Many package managers pack most files in a directory by default. If
.mapfiles are not explicitly excluded in thefilesfield ofpackage.jsonor in ignore rules, map files may be uploaded along with the package. - Debug configuration is mistakenly used as production configuration. If the development/debug and production/release profiles in build scripts share the same parameters, a single oversight may allow complete map files intended for debugging to enter the formal release channel.
- No automated validation before release. If CI does not check tarball contents and relies only on human memory or verbal agreements, the risk can persist for a long time.
A typical bad configuration looks like this: it only cares about generating source maps and has no exclusion logic.
{"name": "some-tool","scripts": {"build": "tsc && webpack --devtool source-map"}}
This configuration is not wrong by itself. The problem is that it is reused directly in the release process without additional cleanup of the release artifacts.
4. Real Risks of Source Code Leakage
Some may ask: if only source code is leaked and no user data is involved, how serious is it? In fact, the risks of source code leakage are multi-layered. Whether it constitutes a major risk depends on how much internal information is included in the source code, whether credentials are present, whether internal interfaces are exposed, and how sensitive the business is about code assets.
| Risk dimension | Description | Priority |
|---|---|---|
| Intellectual property and competitive advantage | Internal scheduling logic, prompt orchestration, tool invocation strategies, and architecture design may become transparent | High |
| Exposure of unpublished capabilities | Internal codenames, experimental modules, and roadmap features may be leaked with the source code, disrupting product rhythm or revealing strategic intent | High |
| Expanded attack surface | If the source code contains internal interfaces, secrets, hardcoded credentials, or key business logic, attackers may perform static analysis and look for exploitable points | High |
| Supply chain collateral risk | If it contains build scripts, internal service addresses, private dependency coordinates, or keys, it may enable further lateral probing | Extremely high |
| Public relations and compliance | Leakage of unpublished information may create external explanation costs and compliance review | Medium |
It must be emphasized: even if the source code itself has no obvious vulnerability, once the moat of “security through obscurity” is broken, defenders may be forced to defend while opponents better understand their implementation. Especially when the source code contains hardcoded keys, internal domains, or private dependency coordinates, it should be handled as a credential leak incident first, not merely as a code publication issue.
5. Pre-Release Protection Checklist: Write “No Leak by Default” into the Pipeline
For source map leakage, the most effective approach is to set up multiple checkpoints in the release process to achieve “no leak by default,” rather than relying on a single measure to be foolproof.
1. Use an allowlist to precisely control release artifacts
Explicitly declare the files to be published in package metadata, and exclude map files as much as possible. Also use prepublishOnly to perform a second cleanup before publishing.
{"name": "some-tool","files": ["dist/**", "!dist/**/*.map", "README.md"],"scripts": {"prepublishOnly": "node scripts/strip-maps.js && npm test"}}
Note that whether negative glob syntax in the files field works reliably depends on the npm version, negative glob support, and whether other configurations such as .npmignore exist. Therefore, the allowlist should not be treated as the only defense. If a team has strict requirements for release artifacts, it is recommended to also use prepublishOnly to delete map files, or to use a separate release directory as a fallback.
Example scripts/strip-maps.js:
const fs = require("fs");const path = require("path");function stripMapFiles(dir) {if (!fs.existsSync(dir)) return;for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {const fullPath = path.join(dir, entry.name);if (entry.isDirectory()) {stripMapFiles(fullPath);} else if (fullPath.endsWith(".map")) {fs.rmSync(fullPath, { force: true });}}}stripMapFiles(path.resolve("dist"));
If CI directly executes npm pack, it is recommended to explicitly run this cleanup script before packing as well, to avoid the cleanup logic not running due to lifecycle differences.
2. Separate build profiles to prevent debug configuration from entering the release process
Debug and production should use different configurations. If a production build only needs a small amount of position information for private error tracking, prioritize not inlining source content. If map files are generated, make sure they do not enter the release directory, or upload them only to a private error tracking platform.
Using webpack as an example, you can use nosources-source-map:
webpack --mode production --devtool nosources-source-map --output-path ./dist
After generation, it is still recommended to move .map files out of the release directory:
mkdir -p ./mapsfind ./dist -name "*.map" -exec mv {} ./maps/ \;
If the team uses hidden-source-map, special care is required: hidden-source-map only avoids writing a sourceMappingURL comment in the output, and does not guarantee that the .map file has no sourcesContent. Therefore, a more reliable approach for production builds is to use nosources-source-map, or strip sourcesContent before uploading to a private platform. At the same time, regardless of the approach, the step of moving map files out of dist should be retained to prevent them from being packaged with the release artifacts.
3. Use dry-run to verify the final manifest before release
Before actually uploading, use npm pack --dry-run or npm publish --dry-run to print the list of files that will be packed or published. The two are slightly different: npm pack --dry-run mainly previews packing content and the file list without generating a tarball; npm publish --dry-run mainly simulates the release process and usually does not produce a directly scannable tarball. Therefore, in CI, it is recommended to combine output checks with actual package scanning.
if npm pack --dry-run | grep -qE '[.]map'; thenecho "The release package contains source maps; publishing is prohibited"exit 1fi
This check can serve as a quick gate, but a more reliable approach is still to generate a tarball and scan its contents in the next step.
4. Automatically scan tarballs before release
In the CI release task, you can first generate the release package, then scan the tarball contents. If a map file is found, fail immediately.
rm -rf ./release-tarballsmkdir -p ./release-tarballsnpm pack --pack-destination ./release-tarballsfor tarball in ./release-tarballs/*.tgz; doif tar -tzf "$tarball" | grep -qE '[.]map'; thenecho "Source map detected; publishing is prohibited"exit 1fidone
This script directly checks the contents of the compressed package that is about to be published, which is closer to the final release result than relying solely on the files field.
5. Scan for secrets and sensitive information
A source map leak may be only one entry point into supply chain problems. If source code, build scripts, or map files contain credentials, internal addresses, or private dependency coordinates, the risk will be further amplified. Therefore, it is recommended to integrate credential scanning tools into the pipeline and intercept suspected secrets at three checkpoints: commit, merge, and release.
# Scan the working directory and history for suspected secrets before commitgitleaks detect --source . --verbose# Or use trufflehog to scan the file system and report only verifiable credentialstrufflehog filesystem . --only-verified
If a suspected real credential is found, it should be handled as leaked: immediately rotate the related keys, tokens, and access credentials.
6. Artifact size anomaly alerts
Set thresholds for release package size. An artifact that suddenly expands to tens of megabytes is often a strong signal that map files, debug information, or temporary files have been accidentally packed. It is recommended to add size monitoring in CI: when the tarball size exceeds historical baselines, thresholds, or the common range of similar versions, trigger an alert or automatically block the release.
6. Emergency Response After a Leak
If the problematic version has already been published, time is critical. It is recommended to handle it in the following order:
- Unpublish immediately. Remove the problematic version from the public registry immediately, and use
unpublishordeprecateto prevent new users from obtaining it. - Understand the limitations of unpublishing.
unpublishordeprecatecannot stop copies that have already been downloaded, mirrored, or cached. Public registry mirrors, CDN caches, third-party aggregation sites, and even local machines may still retain backups. Therefore, the incident should be handled under the premise that “the source code has already leaked and cannot be recalled,” rather than hoping for complete deletion. - Prioritize investigation of hardcoded keys, internal interfaces, and private dependency coordinates. If the source code may contain access tokens, private keys, internal service addresses, private package addresses, or database connection information, immediately assess and rotate related credentials. If internal interfaces are involved, check at the same time whether there are unauthorized calls or abnormal access.
- Assess the exposure scope. Review which internal information is contained in the reconstructed source code, such as architecture, interfaces, roadmap, algorithm strategies, or experimental features, and judge the impact on business, public relations, and compliance accordingly.
- Fix the root cause and harden the process. Identify which configuration step caused map files to leak, and add automated scanning, allowlists, profile isolation, and pre-release gates to prevent recurrence.
- Preserve complete audit records. Record versions, timelines, handling actions, notification targets, and fix results for internal retrospectives and compliance reviews.
7. Conclusion
The greatest value of this incident is not merely observing the spectacle, but a reminder to all engineering teams: in the era of automated build and distribution, source code leakage often does not come from a compromised server, but from a misconfigured artifact. Source maps are a valuable troubleshooting tool, but they can also become a vehicle that “packs away” source code.
The truly reliable defense is to treat release artifacts as assets that require least-privilege governance: allowlist release, isolate profiles, clean with prepublishOnly, verify with dry-run, scan tarballs, block sensitive information, and alert on abnormal artifact size. Only when these checkpoints work together can teams enjoy the performance benefits of compression and transpilation without giving away source code. Security is not a one-time patch; it is making “no leak by default” a part of every pipeline step.
