☰
Anthropic 把自家核心代码“裸奔”了——Claude Code 源码泄露事件全解析:从 npm Source Map 到 TaoToken 的排查复盘
2026/10/18 0:16:33 网站建设 项目流程

1. 一个 .map 文件如何把 51 万行源码送上公网

2026 年 3 月 31 日早上,Twitter 用户 @Fried_rice 发了一条推文,内容很短:Claude Code 的源码通过 npm registry 里的一个 map 文件泄露了。这条消息在 AI 开发者圈子里迅速传开,因为泄露的不是某个配置文件或某个模块,而是 Claude Code 完整的核心源码——约 1900 个文件、512000+ 行 TypeScript 代码。

如果你平时用 Claude Code 写代码,或者你所在的团队正在做 npm 包发布,这件事值得你花十分钟认真看一下。它不是一个“黑客攻破服务器”的故事,而是一个打包流程配置疏漏的典型案例。Source Map 文件(.map)本来是前端和 Node.js 开发中的调试辅助文件,它能把混淆或压缩后的代码映射回原始源码,方便开发者在浏览器或 Node 中调试。问题在于,Anthropic 在发布 Claude Code v2.1.88 的 npm 包时,忘记把 Source Map 文件从发布产物中删掉,而这个 .map 文件直接引用了存放在 R2 存储桶上的完整未混淆 TypeScript 源码。

换句话说,任何人只要会解析 .map 文件,就能把 Claude Code 完整的 src/ 目录下载下来。不是被入侵,不是内部人员泄露,就是一个打包流程的配置疏漏。这件事对做工程的团队来说,最大的价值不是“吃瓜”,而是借这个机会检查一下自己的 npm 发布流程里有没有同样的隐患。我试过用几条命令快速检查一个 npm 包的发布产物,下面会把完整的排查步骤和审计清单写出来,你可以直接复制到自己的项目里用。

这次泄露暴露的技术栈也很有意思:Bun 运行时 + React + Ink(终端 UI)+ Commander.js + Zod v4 + ripgrep + OpenTelemetry + GrowthBook + OAuth 2.0。Anthropic 用 React 来写终端 UI,用的是 Ink 这个库,把 React 的组件模型搬到命令行里渲染。核心入口是 src/main.tsx,用 Commander.js 解析命令行参数,初始化 React + Ink 渲染器。最重量级的文件是 src/QueryEngine.ts,46000 行,处理流式响应、工具调用循环、思考模式、重试逻辑和 Token 计数。工具系统大约 40 个工具,每个工具都是自包含的模块,定义自己的输入 Schema、权限模型和执行逻辑。

从工程复盘的角度看,这次事件的核心问题可以拆成三层:第一层是 Source Map 生成机制本身没有错,错的是发布产物没有做过滤;第二层是 CI/CD 流水线里缺少发布前审计步骤;第三层是团队对 npm 包发布产物的边界没有明确清单。下面我会从排查命令、审计清单、调用侧配置核验三个方向展开,帮你定位自己的构建产物是否暴露源码。

2. 用 npm pack 和 source-map 解析命令定位泄露面

要判断一个 npm 包是否把 Source Map 和源码一起发布了,最直接的方式不是看 node_modules 里的文件,而是看 npm registry 上实际发布的 tarball 内容。因为 node_modules 里的文件可能被本地构建覆盖,而 registry 上的 tarball 才是真正对外公开的产物。

你可以用下面这条命令下载某个包在 registry 上的 tarball 并列出内容,不需要安装,也不会污染本地环境:

# 下载指定包和版本的 tarball 到当前目录 npm pack @anthropic-ai/claude-code@2.1.88 # 列出 tarball 中的所有文件,筛选 .map 和 src 相关文件 tar -tzf anthropic-ai-claude-code-2.1.88.tgz | grep -E '\.map$|/src/'

如果输出里出现 .map 文件,尤其是体积较大的 .map 文件,就需要进一步检查它是否包含 sourcesContent 字段。sourcesContent 是 Source Map 规范里的一个可选字段,如果它被填充,就意味着原始源码被直接内嵌在 .map 文件里,任何人拿到这个文件就能还原出完整源码。

下面这段 Node.js 脚本可以解析 .map 文件并检查是否包含源码内容:

// check-sourcemap.js // 用法: node check-sourcemap.js path/to/file.map const fs = require('fs'); const path = require('path'); const mapPath = process.argv[2]; if (!mapPath) { console.error('请传入 .map 文件路径'); process.exit(1); } const raw = fs.readFileSync(mapPath, 'utf8'); const map = JSON.parse(raw); console.log('文件:', path.basename(mapPath)); console.log('version:', map.version); console.log('sources 数量:', Array.isArray(map.sources) ? map.sources.length : 0); console.log('sourcesContent 是否存在:', Array.isArray(map.sourcesContent)); if (Array.isArray(map.sourcesContent)) { const totalChars = map.sourcesContent.reduce((sum, s) => sum + (s ? s.length : 0), 0); console.log('sourcesContent 总字符数:', totalChars); console.log('前 3 个 source 路径:'); map.sources.slice(0, 3).forEach((s, i) => console.log(` [${i}] ${s}`)); }

如果 sourcesContent 存在且总字符数很大,基本可以确认源码已经随包发布。接下来要检查的是 package.json 里的 files 字段和 .npmignore 配置。npm 的发布规则是:如果 package.json 里有 files 字段,只有 files 里列出的文件会被发布;如果没有 files 字段,则遵循 .npmignore 或 .gitignore 的排除规则。很多团队的问题就出在这里——files 字段写得太宽泛,比如写了"files": ["dist", "lib", "src"],而 dist 目录里恰好包含了构建生成的 .map 文件。

你可以用下面这条命令检查一个已安装包的 package.json 中 files 字段和实际发布文件的关系:

# 查看本地已安装包的 package.json 中 files 字段 node -e "const p=require('./node_modules/@anthropic-ai/claude-code/package.json'); console.log(JSON.stringify(p.files, null, 2))" # 对比 npm registry 上实际发布的文件列表 npm view @anthropic-ai/claude-code@2.1.88 dist.tarball

拿到 tarball URL 后,可以直接下载并解压检查。这一步的关键是:不要只看本地构建产物,一定要看 registry 上的实际发布内容。因为本地可能有 .npmignore 或构建脚本做了过滤,但发布时的配置可能不一致。

另外,如果你的项目使用 TypeScript 编译,tsconfig.json 里的 sourceMap 和 inlineSources 选项会直接影响 .map 文件的生成。下面是一个需要重点检查的配置片段:

{ "compilerOptions": { "sourceMap": true, "inlineSources": true, "declarationMap": true } }

inlineSources 为 true 时,TypeScript 会把源码内容直接写入 .map 文件的 sourcesContent 字段。如果你的发布流程没有在打包后删除 .map 文件,或者没有在 files 字段里排除 .map,源码就会随包发布。建议在发布前用脚本做一次产物审计,把 .map 文件和包含 sourcesContent 的文件列为阻断项。

3. 发布前产物审计清单与 TaoToken 调用侧配置核验

排查完发布产物之后,下一步是建立一套可重复执行的发布前审计流程。下面这份清单可以直接放进你的 CI 脚本或 pre-publish 钩子里,按顺序执行。

第一步,检查 tarball 内容。在 npm publish 之前,用npm pack --dry-run可以看到即将发布的文件列表。但更稳妥的方式是实际打包后检查:

# 实际打包到临时目录 npm pack --pack-destination /tmp/npm-audit # 列出 tarball 内容并检查 .map 文件 tar -tzf /tmp/npm-audit/*.tgz | grep -E '\.map$' && echo "发现 .map 文件,需要处理" || echo "未发现 .map 文件"

第二步,检查 .map 文件是否包含 sourcesContent。如果上一步发现了 .map 文件,用前面写的 check-sourcemap.js 脚本批量检查:

# 解压 tarball 后批量检查 mkdir -p /tmp/npm-audit/extracted tar -xzf /tmp/npm-audit/*.tgz -C /tmp/npm-audit/extracted find /tmp/npm-audit/extracted -name '*.map' -exec node check-sourcemap.js {} \;

第三步,检查 package.json 的 files 字段是否显式排除了 .map。推荐的做法是在 files 字段里只列出必要的产物目录,并且在构建脚本里加一步删除 .map 文件:

{ "files": [ "dist/**/*.js", "dist/**/*.d.ts", "!dist/**/*.map" ], "scripts": { "build": "tsc && npm run clean:maps", "clean:maps": "find dist -name '*.map' -delete", "prepublishOnly": "npm run build && npm run audit:pack" } }

第四步,在 CI 里加一个阻断步骤。如果发现 .map 文件包含 sourcesContent,直接让流水线失败。下面是一个 GitHub Actions 的片段示例:

- name: Audit npm pack for source maps run: | npm pack --pack-destination /tmp/npm-audit if tar -tzf /tmp/npm-audit/*.tgz | grep -qE '\.map$'; then echo "ERROR: .map files found in package tarball" exit 1 fi

完成发布侧审计后,调用侧的配置核验同样重要。如果你通过 TaoToken 统一管理 API Key 和调用通道,可以在发布前用一次最小请求验证配置是否正确。TaoToken 的 API 地址是 https://taotoken.net/api,你可以在环境变量里统一配置 Base URL 和 Key,避免在代码里硬编码。

下面是一个 Node.js 环境下的配置核验脚本,用来确认调用通道和模型 ID 是否可用:

// verify-taotoken.js // 用法: TAOTOKEN_API_KEY=sk-xxx node verify-taotoken.js const apiKey = process.env.TAOTOKEN_API_KEY; if (!apiKey) { console.error('请设置 TAOTOKEN_API_KEY 环境变量'); process.exit(1); } const baseUrl = 'https://taotoken.net/api'; const modelId = 'claude-sonnet-4-20250514'; async function verify() { const res = await fetch(`${baseUrl}/v1/messages`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': apiKey, 'anthropic-version': '2023-06-01' }, body: JSON.stringify({ model: modelId, max_tokens: 32, messages: [{ role: 'user', content: 'reply with ok' }] }) }); console.log('HTTP status:', res.status); const data = await res.json(); console.log('response:', JSON.stringify(data).slice(0, 200)); } verify().catch((err) => { console.error('verify failed:', err.message); process.exit(1); });

如果你使用 Claude Code 或类似的编码工具,可以在 settings 配置文件里统一指定 Base URL 和 Key。下面是一个 settings.json 片段,路径和字段名需要与你的工具版本一致:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_API_KEY": "sk-your-key-here", "ANTHROPIC_MODEL": "claude-sonnet-4-20250514" } }

这里的三件套是 Base URL、Key、Model ID,缺一不可。Base URL 指向 https://taotoken.net/api,Key 从控制台生成,Model ID 根据你实际使用的模型填写。配置完成后,用上面的 verify-taotoken.js 脚本发一次最小请求,确认返回 200 且响应体里包含正常内容。如果返回 401,说明 Key 无效或没有正确传递;如果返回 404,检查 Base URL 是否多了或少了路径段。

对于需要长期编码和 Agent 场景的团队,可以在 TaoToken 的 Coding Plan 里统一管理调用配额和模型切换,避免每个开发者各自维护 Key。模型对话入口可以用来快速验证某个模型 ID 是否可用,API Keys 页面用来生成和轮换 Key,接入文档里有不同语言和工具的配置示例。发布前把调用侧核验和产物审计一起跑一遍,基本能覆盖大部分配置类问题。

4. 验证请求与成功结果:从 401 到正常返回的完整链路

配置写完之后,最关键的一步是实际发一次请求,确认整条链路是通的。很多团队的问题不是配置写错了,而是配置写对了但环境变量没有生效,或者请求发到了错误的端点。下面我用一个完整的验证流程,从环境变量检查到请求发送,再到响应解析,把每一步的预期结果写清楚。

先检查环境变量是否在当前 shell 会话中生效:

# 检查环境变量 echo "BASE_URL: $ANTHROPIC_BASE_URL" echo "KEY_PREFIX: ${ANTHROPIC_API_KEY:0:8}" echo "MODEL: $ANTHROPIC_MODEL"

预期输出是 BASE_URL 显示 https://taotoken.net/api,KEY_PREFIX 显示 Key 的前 8 位,MODEL 显示你配置的模型 ID。如果 BASE_URL 为空,说明环境变量没有导出到当前会话,需要检查 .bashrc、.zshrc 或 CI 的 env 配置。

接下来用 curl 发一次最小请求,这是最直接的验证方式:

curl -sS -X POST "https://taotoken.net/api/v1/messages" \ -H "Content-Type: application/json" \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "'"$ANTHROPIC_MODEL"'", "max_tokens": 32, "messages": [{"role": "user", "content": "reply with ok"}] }' | head -c 500

如果返回的 JSON 里包含"content"字段和正常的文本内容,说明调用通道是通的。如果返回 401,检查 Key 是否正确、是否有多余空格、是否在请求头里正确传递。如果返回 404,检查 Base URL 是否写成了 https://taotoken.net/api/v1 之外的其他路径。如果返回 400,检查 model ID 是否拼写正确、请求体是否符合 Messages API 的格式。

对于使用 Claude Code 的场景,可以在终端里直接运行一次非交互式请求来验证:

# 非交互式验证,输出模型返回 claude -p "reply with ok" --model "$ANTHROPIC_MODEL"

如果这条命令能正常输出 ok,说明 Claude Code 的配置已经生效。如果报错,根据错误信息定位:如果是认证错误,检查 ANTHROPIC_API_KEY;如果是连接错误,检查 ANTHROPIC_BASE_URL;如果是模型不存在,检查 ANTHROPIC_MODEL。

下面是一个成功响应的示例结构,你可以对照自己的返回结果:

{ "id": "msg_01Xxx", "type": "message", "role": "assistant", "content": [ { "type": "text", "text": "ok" } ], "model": "claude-sonnet-4-20250514", "stop_reason": "end_turn", "usage": { "input_tokens": 12, "output_tokens": 3 } }

关键字段是content数组里有text类型的内容,stop_reason为end_turn或max_tokens,usage里有 input_tokens 和 output_tokens。如果返回体里没有 content 字段,或者 content 为空数组,需要检查请求参数是否被正确解析。

验证通过之后,建议把这次请求的配置固化到项目的 .env.example 或配置模板里,让团队成员可以直接复制。同时把验证脚本放进 CI 的 smoke test 阶段,每次发布前自动跑一次,确保调用通道没有因为 Key 轮换或配置变更而失效。对于需要长期运行的 Agent 场景,可以在 Coding Plan 里配置多个模型 ID 和对应的配额,避免单点故障。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

在配置和验证过程中,有几类报错出现频率最高。下面按报错信息逐条对照,给出原因和修复方式。

401 Unauthorized / invalid api key

这是最常见的报错,通常有三个原因:Key 没有正确传递、Key 已失效或被轮换、请求头字段名写错。Messages API 使用x-api-key请求头,如果你写成了Authorization: Bearer,就会返回 401。检查方式:

# 确认请求头字段名 curl -sS -X POST "https://taotoken.net/api/v1/messages" \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "Content-Type: application/json" \ -d '{"model":"'"$ANTHROPIC_MODEL"'","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}'

如果 Key 是从控制台新生成的,确认复制时没有包含前后空格。如果 Key 是在 CI 里通过 secret 注入的,确认 secret 名称和引用方式一致。

local proxy failed / connection refused

这个报错通常出现在本地开发环境,原因是 Base URL 指向了本地代理地址,但代理服务没有启动。检查环境变量:

echo $ANTHROPIC_BASE_URL

如果输出是http://localhost:xxxx或http://127.0.0.1:xxxx,说明配置指向了本地代理。把 Base URL 改为 https://taotoken.net/api 后重新验证。如果你确实需要使用本地代理做请求转发,确认代理服务已启动并监听正确端口。

reading choices / unexpected response format

这个报错通常出现在使用 OpenAI 兼容格式的客户端调用 Anthropic 接口时。Anthropic 的 Messages API 返回结构是content数组,而 OpenAI 的 Chat Completions API 返回结构是choices数组。如果你的代码里读取response.choices[0].message.content,就会报 reading choices 相关的错误。修复方式是改用 Anthropic 的响应解析逻辑:

// Anthropic Messages API 响应解析 const text = data.content .filter((block) => block.type === 'text') .map((block) => block.text) .join('');

如果你使用的工具同时支持两种格式,确认请求端点是否正确。Anthropic 格式的端点是/v1/messages,OpenAI 兼容格式的端点通常是/v1/chat/completions。

OAuth token expired / authentication failed

Claude Code 在某些版本里使用 OAuth 做认证,如果 OAuth token 过期或刷新失败,会报认证错误。检查方式:

# 查看 Claude Code 的认证状态 claude auth status

如果显示未认证或 token 过期,重新执行登录流程。如果你通过 API Key 方式调用,确认没有同时配置 OAuth 和 API Key 导致冲突。在 settings.json 里,ANTHROPIC_API_KEY和 OAuth 配置二选一,不要同时启用。

模型不存在 / model not found

这个报错的原因是 Model ID 拼写错误或该模型在当前通道不可用。检查方式:

# 用最小请求验证模型 ID curl -sS -X POST "https://taotoken.net/api/v1/messages" \ -H "x-api-key: $ANTHROPIC_API_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "Content-Type: application/json" \ -d '{"model":"'"$ANTHROPIC_MODEL"'","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}'

如果返回 404 且错误信息里包含 model 相关字段,确认 Model ID 是否与控制台或文档里列出的一致。Model ID 区分大小写,不要手动拼写,直接从文档复制。

发布产物里仍然有 .map 文件

如果你已经按前面的审计清单配置了 files 字段和 clean:maps 脚本,但 npm pack 后仍然发现 .map 文件,检查两个地方:一是构建脚本的执行顺序,确认 clean:maps 在 tsc 之后执行;二是 .npmignore 和 files 字段的优先级,files 字段的排除规则可能被 .npmignore 覆盖。最稳妥的方式是在 prepublishOnly 钩子里加一步实际打包检查,发现 .map 文件直接退出并报错。

{ "scripts": { "prepublishOnly": "npm run build && npm run audit:pack", "audit:pack": "npm pack --pack-destination /tmp/npm-audit && tar -tzf /tmp/npm-audit/*.tgz | grep -E '\\.map$' && exit 1 || exit 0" } }

上面这条命令的逻辑是:打包后检查 tarball 里是否有 .map 文件,如果有则 exit 1 阻断发布,如果没有则 exit 0 继续。注意 grep 的退出码逻辑,grep -q找到匹配返回 0,找不到返回 1,所以需要根据实际需求调整判断条件。

6. 把审计和核验固化到日常流程

这次 Claude Code 源码泄露事件给工程团队最大的提醒,不是“Anthropic 也会犯错”,而是发布产物的边界需要被显式定义和自动检查。一个 .map 文件就能把 51 万行源码送上公网,说明发布流程里缺少一道针对产物内容的审计关卡。

你可以从今天开始做三件事:第一,在项目的 prepublishOnly 钩子里加一条 .map 文件检查命令,发现即阻断;第二,在 CI 里加一个 smoke test,用最小请求验证调用通道和模型 ID 是否可用;第三,把 Base URL、Key、Model ID 三件套统一到环境变量或配置模板里,避免硬编码和配置漂移。

如果你通过 TaoToken 管理调用通道,可以在 API Keys 页面生成和轮换 Key,在接入文档里找到不同工具和语言的配置示例,在模型对话页面快速验证模型 ID 是否可用。对于需要长期编码和 Agent 场景的团队,Coding Plan 提供了统一的配额和模型管理入口,减少每个开发者单独维护配置的成本。

发布前跑一遍产物审计,调用前跑一遍配置核验,这两步加起来花不了几分钟,但能挡住大部分低级但影响面很大的问题。源码泄露的根因往往不是技术难题,而是流程里少了一个检查步骤。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询