Claude Code Source Code Leaked via npm Source Map: A Field Analysis
Someone on Hacker News noticed that @anthropic-ai/claude-code shipped .map files in its published npm tarball. A source map contains the original, pre-bundle TypeScript. If you had claude-code installed, you already had Anthropic's internal source on your machine. No exploit required, no credentials needed. Just npm install.
This is not a novel attack class. It is a build pipeline hygiene failure, the kind that is embarrassingly easy to make and equally easy to prevent. What makes it interesting here is the target: Anthropic spent considerable effort distributing a compiled, opaque binary-style CLI, and a single missing exclusion rule undid all of that.
How Source Maps Become Accidental Disclosures
During a TypeScript or bundler build (esbuild, tsup, Rollup, webpack), the toolchain can emit a .js.map file alongside every compiled artifact. That file contains two critical fields: sources, a list of original file paths, and sourcesContent, an array of the actual original source strings for each file.
The intent is developer ergonomics. A map file lets your browser's or Node's debugger show you TypeScript line numbers against an error in the compiled output. You generate them locally during development, or ship them to an internal error-monitoring service like Sentry where they stay private.
The mistake is letting them land in files when you npm publish. Once a package version is published to the public registry, it is immutable. Unpublish is possible within 72 hours under npm's policies, but the content is already mirrored by multiple package CDNs (jsDelivr, unpkg, the Cloudflare-backed registry mirrors). Removal is never complete.
What the Claude Code Package Shipped
@anthropic-ai/claude-code is distributed as a compiled JavaScript CLI. The published tarball included .map files that referenced the original TypeScript source tree. Those map files had sourcesContent populated, meaning the original .ts files were embedded inline. You did not need to reconstruct anything; the source was sitting there in a JSON array.
The leak covered whatever was in scope at build time: internal logic, prompt construction, API call patterns, retry/backoff behavior, feature flags, and any hard-coded strings that Anthropic kept out of the public documentation. We have not independently catalogued the full contents, but anyone who ran npm install -g @anthropic-ai/claude-code before the fix had it cached locally.
Extracting Source From a Published .map File
This takes about three minutes. For any npm package, the workflow is:
# Download the tarball without installing
npm pack @anthropic-ai/claude-code 2>/dev/null
tar -xzf anthropic-ai-claude-code-*.tgz
# Find map files
find package/ -name "*.map"
# Reconstruct original sources from a map file
node - <<'EOF'
const fs = require('fs');
const path = require('path');
const mapFile = process.argv[2] || 'package/dist/cli.js.map';
const map = JSON.parse(fs.readFileSync(mapFile, 'utf8'));
console.log(`Sources: ${map.sources.length}`);
console.log(`Has sourcesContent: ${Boolean(map.sourcesContent?.length)}`);
map.sources.forEach((src, i) => {
const content = map.sourcesContent?.[i];
if (!content) return;
const outPath = path.join('reconstructed', src.replace(/^(\.\.\/)*/g, ''));
fs.mkdirSync(path.dirname(outPath), { recursive: true });
fs.writeFileSync(outPath, content);
});
console.log('Done. Check ./reconstructed/');
EOF
Run that against the affected package version and you get the full source tree written to disk. No reverse engineering, no decompilation, no guesswork.
The Build Pipeline Failure Mode
The proximate cause is almost always one of three things: sourcemap: true left in the bundler config for production builds, a files field in package.json that uses a glob too broad (dist/** instead of dist/**/*.js), or a CI publish step that runs without a clean --production flag and picks up dev artifacts.
Anthropic's toolchain appears to have been tsup or esbuild, both of which default to generating source maps unless explicitly told not to. The likely sequence: source maps were enabled for internal development, the files field was either absent (defaulting to everything) or included a wildcard that matched .map, and the publish step did not strip them.
The fix is straightforward. In the bundler config:
// tsup.config.ts
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
// Never ship source maps in public packages.
// Use sourcemap: 'inline' only for internal error-monitoring, never published.
sourcemap: false,
clean: true,
})
And in package.json, be explicit rather than permissive:
{
"files": [
"dist/**/*.js",
"dist/**/*.cjs",
"dist/**/*.mjs",
"dist/**/*.d.ts",
"README.md"
]
}
The files whitelist is the last line of defense. Even if the build emits maps, they never make it into the tarball. Treat it as a required field on every package you publish, not an optional optimization.
What This Means for Supply-Chain Consumers
If you run Claude Code in an agent pipeline, a CI environment, or a multi-cloud automation workflow, the source exposure changes your threat model slightly. Internal prompt formats, API call structures, and feature-flag names are now public. A motivated attacker could craft payloads specifically shaped to exploit known internal behavior, or look for edge cases in the retry logic that would not be visible from the compiled output alone.
The short-term mitigation is the same as for any supply-chain event: pin to a known-clean version in your package.json, run npm audit signatures to verify package integrity, and watch for unexpected behavior in your Claude Code integration after any version bump. We cover supply-chain hygiene as part of our detection engineering practice, including continuous monitoring of your npm dependency tree for integrity anomalies.
Auditing Your Own Packages Before You Ship
If you publish npm packages, run this before every release:
# Dry-run publish and inspect what would be included
npm pack --dry-run 2>&1 | grep -E "\.(map|ts)$"
# If you see any .map or raw .ts files, you have a problem.
# Fix the bundler config or the files field, then re-check.
# Also check for accidentally bundled secrets
grep -r "process.env\." dist/ | grep -v "process.env.NODE_ENV"
Make this a required CI gate. A single grep step in your publish workflow that fails on any .map file in the tarball costs nothing and prevents exactly this class of disclosure. Some teams go further and run secretlint or trufflehog against the packed tarball as a pre-publish check, which catches API keys or tokens that crept in through sourcesContent.
The broader lesson is that compiled distribution is not a security boundary. Bundlers, source maps, and tree-shaking all operate below the level of the package manifest. You need explicit controls at the publish layer, not just at the build layer.
Next Steps
If you need a supply-chain audit of your published packages, a hardened publish pipeline, or help operationalizing integrity monitoring across your npm estate, reach out to us at /contact.
