Claude Code 源码泄露始末:怎么泄的、怎么提的、怎么跑的
这两天 AI 圈最大的瓜,不是哪家又发新模型了,而是 Anthropic 把 Claude Code 的完整源码给泄了。
不是被黑客攻破,不是内鬼出卖,而是自己在发布 npm 包的时候,把源码的”对照表”一起打包发了出去。相当于卖手机的时候,把设计图纸和零件清单塞进包装盒寄给了用户。
花了两天研究整个事件的来龙去脉,顺便把源码提取、重建运行的过程摸了一遍。今天就来聊聊这件事。
━━━━━━━━━━━━━━━━
🔥 事件始末
3 月 31 日,安全研究员 Chaofan Shou 发了一条推文:Claude Code 的源码通过 npm 注册表里的一个 map 文件泄露了。
这个 map 文件有 59.8 MB。里面是完整的、未混淆的 TypeScript 源码——1900 个源文件,51.2 万行代码。
消息一出,GitHub 上几小时内就冒出了好几个镜像仓库,其中一个直接涨到了近 3 万 star、4 万 fork。传播速度比 Anthropic 的公关反应快多了。
Anthropic 赶紧做了三件事:更新 npm 移除 source map、删掉旧版本、给 GitHub 镜像发 DMCA 下架通知。但这种事嘛,互联网有记忆。
从泄露的源码里扒出了不少有趣的东西:
-
103 个 feature flag(功能开关),包括 BUDDY(一个电子宠物功能)、KAIROS(后台守护进程)、VOICE_MODE(语音模式) -
Undercover Mode——让 Anthropic 员工往开源项目提交代码时隐藏公司身份的系统 -
完整的多 Agent 协调系统、内部 prompt 模板、工具定义
━━━━━━━━━━━━━━━━
📝 Source Map 是个啥
不搞前端的同学可能对 source map 没什么概念,简单解释一下。
现代 JavaScript 项目在发布前都会做一件事:把源码打包压缩。原本几千个文件、好几万行、带着清晰变量名的代码,会被压缩成一两个巨大的、变量名全变成 a、b、c 的文件。这样做一是为了减小体积,二是让代码不那么容易被人读懂。
但压缩后的代码出了 bug 怎么调试?总不能对着一行 50 万字符的代码找问题吧。这时候就需要 source map——一张对照表,记录着”压缩后的第 X 行对应原始代码的第 Y 个文件第 Z 行”。调试时浏览器或 IDE 用这张表把压缩代码还原成原始源码。
有个关键细节:source map 的 sourcesContent 字段里,直接保存了所有原始源文件的完整内容。拿到 .map 文件,就等于拿到了全部源码。
所以这玩意儿正常是绝对不该出现在发布包里的。Anthropic 这次就是在发布 @anthropic-ai/claude-code 2.1.88 版本时,把 cli.js.map 这个 59.8 MB 的大家伙一起打包发到了 npm 上。
━━━━━━━━━━━━━━━━
🛠️ 动手提取源码
搞明白原理之后,提取源码其实非常简单,总共就四步:
第一步,从 npm 下载发布包:
npm pack @anthropic-ai/claude-code@2.1.88
第二步,解压:
tar -xzf anthropic-ai-claude-code-2.1.88.tgzcd package
第三步,创建提取脚本。新建一个 extract-sourcemap.mjs 文件,从 sorrycc(陈成,Umi.js 作者)写的 gist 复制代码:https://gist.github.com/sorrycc/ec2968c6696f3cba361e3f6c9826e927
脚本干的事情很简单:读 .map 文件 → 遍历 sourcesContent → 跳过 node_modules → 按原始路径把每个源文件写到磁盘上。30 来行代码。
第四步,运行脚本:
node extract-sourcemap.mjs cli.js.map ./extracted-src
跑完之后,extracted-src 文件夹里就是 Claude Code 的完整源码了。
(Anthropic 已经从 npm 移除了这个版本,现在去下可能下不到了。GitHub 上镜像一搜一大把,找不到的可以公众号后台私信”源码”获取。)
━━━━━━━━━━━━━━━━
🔧 拿到源码之后:能看,但跑不起来
有人会问:拿到源码了,是不是直接就能跑?
答案是不能。
打个比方:你拿到的是一家米其林餐厅的全部菜谱手稿——每道菜怎么切、怎么炒、放多少盐,全都写在上面。但你只有手稿,没有厨房,没有食材,手稿上连”先做哪道菜”都没写。
具体来说,提取出的源码缺了这些东西:
|
|
|
|---|---|
package.json |
|
tsconfig.json |
|
|
|
|
|
|
@ant/* 包 |
|
|
|
要让它跑起来,大概需要做 6 步:
- 装 Bun
——Claude Code 是用 Bun 写的,不是 Node.js,用 Node 跑第一行就报错 - 补
package.json
——扫描全部源文件,提取所有 import的外部包名,写成依赖清单 - 补
tsconfig.json
——配置 TypeScript 编译选项和路径别名 - 给 7 个缺失的包做替身
——源码引用了 7 个在 npm 上根本找不到的包,其中 4 个是 Anthropic 内部没有开源的私有模块( @ant/*开头的),另外 3 个虽然代码已经在源码里了,但引用方式是按包名引的,系统找不到。解决思路就是”绕过去”:给每个缺失的包造一个同名的替身,让程序加载时不崩溃就行 - 注入 MACRO + 写启动入口
——往全局变量里塞版本号等默认值,创建一个 5 行的启动文件 bun install && bun run dev
——装依赖,跑起来
关于第 1 步。有人会问:前面提取源码用的明明是 node 命令,怎么跑源码又要用 Bun?
这俩是不同的 JavaScript 运行环境,关系类似于 Chrome 和 Firefox——都能跑 JS,但各有各的独家 API。提取脚本就是个普通的 Node.js 小脚本,用 node 跑没问题。但 Claude Code 的源码里大量使用了 Bun 独有的功能,比如 Bun.file()、Bun.spawn(),还有一个叫 bun:bundle 的内置模块——这些东西 Node.js 压根不认识,所以必须用 Bun 来跑。
再说第 4 步。那 4 个 Anthropic 内部模块(Computer Use 桌面控制、Chrome 浏览器集成等)在核心流程中并不是必需的。它们都躲在 feature() 这个功能开关后面——在非编译模式下,所有开关都返回 false,所以这些高级功能压根不会被执行。替身只需要”存在”就行,不需要真的能用。程序加载时看到这个包名有东西就不会崩,至于里面是不是空的,它不在乎。
如果不想自己折腾,GitHub 上已经有好几个现成的可运行仓库了(搜 claude-code-rev 或 start-claude-code),clone 下来直接跑。
想魔改的话,方向也很多:改 feature flag 打开隐藏功能、添加自定义工具、替换 API 端点接入自己的模型。
━━━━━━━━━━━━━━━━
💡 Anthropic 的回应:出了事故,反而要加速
这件事最让我觉得有意思的,不是泄露本身,而是 Anthropic 的回应态度。
Claude Code 之父 Boris Cherny 公开表示:这次泄露是人工失误导致的,部署流程中有若干手动步骤,其中一个执行错误,源码就跟着发布包出去了。团队已经上线了一些改进措施,正在继续加更多的合理性校验。
关键来了——他说团队的方向是加快流程,而不是增加更多手续。具体做法是提升自动化程度,并且用 Claude 来检查发布结果。
这个思路挺反直觉的。
大多数公司遇到这种事故,本能反应是什么?加审批、加 checklist、加人工复核。出了一次事故加一层锁,出两次事故加两层锁。结果流程越来越重,发布越来越慢,最后大家开始想办法绕过流程——然后又出事故,形成恶性循环。
Anthropic 的思路正好相反:人会犯错,那就减少人的参与;靠人眼校验容易漏,那就让 AI 来检查。不是出了事就加锁减速,而是让机器跑得更快、更自动、更靠谱。
这跟 DevOps 圈的一个经典理念一脉相承:系统出了 bug,解决方案不是减少部署频率,而是更频繁地部署 + 更快地回滚。部署越频繁,每次变更越小,出问题越容易定位,回滚越快风险越低。
对我们普通开发者来说,这个思路也值得借鉴。下次项目出了事故,开复盘会的时候,与其讨论”要不要加一个审批人”,不如先想想”能不能把出错的那个手动环节自动化掉”。加人是线性成本,加自动化是一次性成本。
用他们自己的 AI 产品来检查自己的发布结果,这也算是一种另类的”吃自己的狗粮”了。
━━━━━━━━━━━━━━━━
🎤 尾声
整件事最大的讽刺可能是:Anthropic 一直在强调 AI Safety,结果自己的代码安全翻车了。不过话说回来,这种发布时手滑带上了不该带的文件的事故,在工程界真不算罕见,只不过这次影响面大了点。
对于 Claude Code 的用户来说,源码泄露其实不影响正常使用。但对于想深入了解 AI 编程工具内部原理的开发者来说,这 51 万行 TypeScript 代码,确实是份绝佳的学习材料。
好了,写完这篇发现已经快十二点了。就酱。
📌 你觉得 Anthropic “出事故反而加速”的思路靠谱吗?你们团队遇到线上事故一般怎么处理?评论区聊聊。
夜雨聆风