Source Map Leaks: Why One .map File Exposes Your Full Source Code
A source map shipped inside a public package can hand over an entire source tree: no exploit, no privilege escalation, no supply-chain compromise required. This article breaks down how a single .map file reconstructs source code, what is actually sensitive when client code goes public, the five release controls whose absence lets the leak happen, and practical defenses across the build, artifact, CI/CD, storage and code layers, plus a 72-hour response playbook for when a leak has already occurred.
1. Incident Recap: Not Breached, but Published
The industry recently saw another textbook source code leak: the client source of a command-line AI coding tool was fully reconstructed and spread across the internet. No exploit, no privilege escalation, no supply chain poisoning. All the attacker had to do was download one sidecar file, tens of megabytes in size, from a public distribution package.
That file was a source map.
This wasn’t a first offense. An earlier release of the same tool had the exact same mistake, which shows the problem isn’t individual carelessness: the release pipeline has no mandatory verification gate. Understanding that is more useful than following the incident itself.
2. Leak Chain Breakdown: Why One .map File Equals the Full Source Code
2.1 Why Bundled Code Needs a “Translation Table”
Front-end and CLI code is usually transpiled, minified and obfuscated before release. Identifiers shrink to a, b and t, and modules are bundled into a single file. That serves size, not security.
It creates a debugging problem: a production error stack points to line 1, column 84213 of the minified bundle, which engineers can’t work with. So bundlers emit a source map alongside each artifact. It reverse-maps every position in the minified code to the line, column and original identifier of the source file.
2.2 The Source Map File Format
Source maps follow the industry-standard source map specification and are essentially JSON. The key fields:
{"version": 3,"file": "cli.js","sources": ["src/index.ts", "src/agent/loop.ts", "src/auth/token.ts"],"sourcesContent": ["<full source of original file 1>", "<full source of original file 2>", "..."],"names": ["createClient", "streamResponse"],"mappings": "AAAA,SAAS,CAAC,GAAG..."}
Two fields account for the damage:
sources: the full paths of every file that went into the build, which directly expose the project’s directory structure and module layout.sourcesContent: the original source text for each file, in one-to-one order withsources, completely unmodified.
What leaks isn’t some “decompilable form” of the code. It’s the exact source text the build consumed. Recovering the project takes no reverse engineering, just a small traversal script:
const fs = require('fs');const path = require('path');const map = JSON.parse(fs.readFileSync('dist/cli.js.map', 'utf8'));map.sources.forEach((p, i) => {const code = map.sourcesContent?.[i];if (!code) return;const out = p.replace(/^[a-z-]+:\/\/\//, ''); // strip the pseudo-protocol prefix added by the bundlerfs.mkdirSync(path.dirname(out), { recursive: true });fs.writeFileSync(out, code);});
Even if sourcesContent is stripped out, the VLQ encoding in mappings still reveals the complete file tree and the line-by-line correspondence. Together with the bundle itself, variable names and code structure can be reconstructed with high fidelity. So “just delete sourcesContent“ is not an effective mitigation.
2.3 Exposure Points
This comment in the bundle lets anyone who obtains it find the map file automatically:
//# sourceMappingURL=cli.js.map
As long as the .map file can be downloaded directly over HTTP from the same release channel as the bundle, the leak is complete. This incident had an additional amplifier: the map file referenced a public object storage address where build caches were stored. The attacker didn’t even need a script; they could enumerate the directory and pull files in bulk.
2.4 Spread Far Outpaces Response
The spread curve for this kind of data is almost vertical. Within hours of the discovery post, multiple mirror repositories appeared on code hosting platforms and were reshared on social media. One repository reached nearly 100,000 stars within hours, though its contents were later cleared.
For a company, this means there is no response window between “leaked” and “unrecoverable.”
3. Root Cause Analysis: Five Missing Gates in the Release Process
| Stage | Control expected | What was missing |
|---|---|---|
| Build configuration | Production builds explicitly disable map output, or write maps only to a non-published directory | The bundler generated maps by default and nothing overrode it |
| Artifact allowlist | The release manifest lists only required files (an explicit allowlist) | An exclusion-based blacklist was used, and the new file format was overlooked |
| Pre-release verification | Automated content scanning and a size threshold on the tarball | No gate; a tens-of-megabyte sidecar file went straight into the repository |
| Storage | Object storage is private-read, directory listing is disabled, and temporary links expire | The cache directory was publicly listable |
| Runtime | Error reports go to an internal server-side service, and map tables stay on the server | Map tables were distributed along with the client |
There are two core lessons:
First, allowlists beat blacklists. “Exclude this type of file when publishing” depends on engineers always remembering every type that needs excluding. “Publish only the files explicitly listed” cannot go wrong by design.
Second, everything in a distribution package is public information. A distribution channel is not the repository permission system. Access controls on a private repository do nothing for artifacts that users have already downloaded to their machines.
4. Impact Assessment: What Is Actually Sensitive
A lot of discussion lands on the conclusion that “client code could be decompiled anyway.” That’s inaccurate. Code being readable and original source text being exposed are different levels of risk.
Low sensitivity (limited impact): business logic, UI and interaction implementation, internal data structure definitions, design intent in comments. This kind of content can usually be roughly reconstructed through decompilation or traffic capture, so the leak mainly causes competitive-level trouble.
High sensitivity (treat as an incident):
- Hardcoded credentials: AK/SK, private tokens, third-party service API keys, internal signing keys
- High-privilege credentials built into the client, and authentication logic built on the “client is trustworthy” assumption
- Internal service addresses, admin endpoints, debug switches, and backdoor-style entry points for feature flags
- Complete implementations of risk-control, rate-limiting and billing validation logic, which can be used directly to construct bypasses
- Hidden parameters and proprietary protocol fields, which once public render existing protections useless
- Dependency manifests and the known-vulnerable versions within them, which amount to a ready-made attack path map for an attacker
- Details of the build and release pipelines, which can be used for later social engineering or follow-on attacks
Guiding principle: any design that relies on invisibility for security no longer holds once the source code is public.
5. Five-Layer Defense
5.1 Build Layer: Tiered Generation, Per-Environment Handling
// Example build config: production builds do not emit map filesexport default defineConfig({build: {sourcemap: process.env.NODE_ENV === 'production' ? false : 'inline',outDir: 'dist',},});
If production troubleshooting genuinely depends on maps, the right approach is to upload map tables to an internal error monitoring or symbol service, link them by release version, and never ship them in the package. This preserves observability while keeping the sensitive asset inside your network. If maps must be generated, write them to a separate path outside the release directory, and make sure in CI that no upload task ever reads that directory.
5.2 Artifact Layer: Explicit Allowlist and a Pre-Release Dry Run
The package manifest should use an explicit file list rather than ignore rules:
{"files": ["dist/cli.js", "dist/chunk-*.js", "package.json", "README.md"]}
Run a packaging rehearsal in CI before release, and print the resulting artifact manifest for a human-readable audit trail:
npm pack --dry-run --json > pack-report.jsontar -tzf cli-*.tgz | tee artifact-manifest.txt# Gate: no .map / .d.ts.map / .debug files are allowed in the manifest! tar -tzf cli-*.tgz | grep -E '\.(map|map\.json)'
Also set an artifact size threshold: a tens-of-megabyte file inside a pure CLI tool’s release package is itself a strong anomaly signal and should be blocked outright in the pipeline.
5.3 CI/CD Layer: Three Mandatory Gates
- Secret scanning: run regex matching plus high-entropy string detection over artifacts, and block the release on any hit. At minimum, cover cloud provider credential formats, private key headers, JWTs, internal domains, database connection strings and webhook URLs.
- Artifact structure validation: allowlist comparison plus a size threshold plus map file detection.
- Release approval that can’t be bypassed: put the gates at a mandatory point in the release pipeline and prohibit direct uploads from local machines. Release identities should use short-lived credentials scoped to the packages they’re allowed to publish, so a leaked long-term token can’t be used to publish repeatedly.
5.4 Storage and Distribution Layer: Private by Default
- Store build caches, symbol tables and archived artifacts in object storage buckets with private read, with directory listing and static website hosting disabled.
- When you do need external sharing, use expiring temporary links and audit when they’re generated.
- Turn on access log delivery, and set alerts for “bulk pulls from a single IP in a short window,” “access from unusual regions” and “abnormal User-Agent traversing paths.” For example, with Baidu AI Cloud object storage, combining bucket permission restriction, access log retention and cloud monitoring alerts can make “bulk download” behavior detectable instead of invisible.
- Handle CDN caching separately: even after source files are deleted at the origin, edge nodes may still serve old versions, so you need to purge the cache and verify the result.
5.5 Code Layer: From “Invisible” to “Safe Even When Visible”
Obfuscation and minification slow reading and raise the cost. They are not a security boundary. The real defense is removing trust from the client:
// Wrong: a long-lived credential baked into a client artifactconst client = createClient({ apiKey: 'sk-live-xxxx' });// Right: the client holds only a short-lived, least-privilege, revocable credentialconst token = await fetchSessionToken(); // issued by the backend, TTL of a few minutes, bound to device and tenant
Supporting practices: manage secrets centrally in a key management service with rotation support; perform permission checks, billing validation and risk decisions entirely server-side; and validate behavior data reported by clients a second time on the server. Never treat “the client already checked” as a security assumption.
6. Incident Response: The First 72 Hours After a Leak
- Stop the bleeding (minutes): revoke the released version, remove it from distribution channels and purge caches. Rotate any leaked credentials immediately, rather than waiting to confirm abuse.
- Forensics (hours): determine the version, time window and download count of the leaked files. Search access logs to see whether automated bulk pulls occurred. Pin down which release the leaked source belongs to, down to the commit.
- Assessment (within 24 hours): go through the Section 4 checklist item by item for hardcoded credentials, internal endpoints and authentication logic, and confirm whether user data, keys or third-party service credentials are involved.
- Remediation (within the week): close the gaps in the release allowlist and gates, and turn this incident into a test case in the pipeline, which is the most effective way to prevent a repeat of the same kind of failure.
- Postmortem and record-keeping: produce an incident report with a clear timeline, impact scope and remediation items. If user data or third-party credentials were involved, assess notification obligations under the applicable compliance requirements.
7. Common Misconceptions
- “Code is safe once it’s obfuscated.” Obfuscation only raises the cost of reading. The presence of a map file resets that to zero.
- “The repository is private, so artifact leaks don’t matter.” Distribution channels don’t inherit repository permissions. Once a user downloads the artifact, it’s public.
- “Deleting
sourcesContentis enough.”sourcesandmappingsalready reveal the structure, andnamesreveals identifiers, so the code can still be reconstructed with high fidelity. - “We only generate maps in development.” As long as the build script branches on an environment variable, misconfiguration can enable maps in production. Physical separation at the build stage is safer.
- “Internal systems aren’t affected.” Internal artifacts leak too, typically via telemetry from open-source components, departing employees and third-party contractors, so they need the same protection.
8. Conclusion
The real lesson from this incident isn’t that one file was published by mistake. It’s that the security boundary of code assets must rest on process constraints and architectural design, not on the assumption that “nobody will look.” Source maps are highly valuable observability tools, and the correct practice is to keep them server-side. The correct practice for release pipelines is an allowlist plus automated gates. The correct practice for code design is to keep credentials and trust out of the client. Miss any one of the three, and the same kind of incident will recur in another form.
