Anthropic 收购 Oven 后,Claude Code 用运行时写了一篇护城河文章
1 views
2025 年,Anthropic 收购了 Oven——Bun 的母公司。
当时大家的解读是:「Anthropic 想拥有自己的 JavaScript 运行时。」说得通,但没有什么特别的。AI 公司投资基础设施,这在行业里是常态。
然后 Claude Code 的源码流出了。
人们发现它打包成了一个自定义版本的 Bun 可执行文件——1.3.9-canary.51+d5628db23,一个从未出现在公开仓库里的 commit。真正有意思的发现是:这不只是一个打包方式的改变。Anthropic 在这个自定义运行时里,埋了一套从未公开过的请求验证机制。
它用一个 5 位十六进制数(cch)把 Fast mode 锁在了官方 Claude Code 里——不是靠政策,而是靠一道横亘在 JavaScript 层和系统调用层之间的原生代码屏障。
这是一个关于「技术护城河可以挖多深」的故事。
技术实现:藏在 Zig 代码里的盲区
Claude Code 的请求签名机制,藏在 Bun 运行时深处,JavaScript 代码里完全找不到。
第一次追踪:死胡同
逆向团队首先用标准方法追踪代码:mitmproxy 抓请求、Bun 二进制里提取 JavaScript 源码、runtime LLDB 调试。在提取出的近 2MB 压缩 JS 代码里搜索 cch,找到了这样一段:
function u7R(T) { let R = `${VERSION}.${T}`; let A = process.env.CLAUDE_CODE_ENTRYPOINT ?? 'unknown'; return `x-anthropic-billing-header: cc_version=${R}; cc_entrypoint=${A}; cch=00000;`;}cch=00000。永远是零占位符。JavaScript 代码构造了这个 header,然后调用 fetch() 发出去。但签名计算不在这里——整个 JS 调用栈追踪完了,cch 的值没有任何变化。
它在别的地方。
第二次追踪:nativeFetch
Bun 自带一个原生实现的 fetch,不是 Node.js 的 libuv 版本,也不是 Web Standard 的实现。这是 Bun 自己写的网络层,用 Zig 编写,直接编译进了运行时。
Claude Code 使用的恰好是这个自定义构建版里的 nativeFetch。
nativeFetch 在发送请求前做了三个检查:
- URL 路径包含
/v1/messages anthropic-versionheader 存在- 请求 body 里包含
cch=00000占位符
三个条件同时满足时,它在发送前对整个请求 body(连同占位符一起)做了 xxHash64 哈希运算,然后把结果直接写回字符串缓冲区——覆盖掉原来的 00000。
这意味着:从 JavaScript 的视角看,fetch() 返回后,你创建的那个 body 字符串已经被悄悄改写了。

违反规范的操作
JavaScript 的字符串在规范里是不可变(Immutable)的。这不只是风格约定,而是语言规范的一部分。
但 Bun 的 nativeFetch 直接写穿了这条规则。它在 Zig 层面修改了 JSC(JavaScriptCore)的字符串底层缓冲区,而且修改的是共享引用——如果你的代码里有 const alias = body,两个变量指向同一块内存,fetch() 返回后两个都会被改。
更诡异的是:如果你的代码里恰好在任何位置包含 cch=00000 这个字符串(哪怕在完全不相关的上下文里),而且碰巧它通过 fetch() 发往了一个 /v1/messages URL——那个字符串也会被静默修改。影响范围超出了 Claude Code 自身,波及任何在 Bun 运行时里执行这类操作的代码。

xxHash64 算法本身不是秘密——五个初始常量都是标准值,算法是教科书级别的。关键在于它的位置:在编译后的 ARM64 机器码里,无符号,没有源码,没有函数名,LLDB 里显示为 ___lldb_unnamed_symbol18111。要找这个函数?grep 找不到,trace 找不到,唯一的方法是用内存 watchpoint 盯着那个写入位置,等它发生。
这是一种刻意的设计选择:把关键逻辑埋在 JavaScript 永远无法触及的盲区里。
产品护城河:从许可证到运行时锁
技术实现是手段,产品的逻辑才是驱动力。
Fast mode 是 Claude Code 的一项研究预览功能,允许在模型推理时跳过某些中间步骤,把响应速度提升一个量级。体验上差异明显,但 Anthropic 并没有把它做成付费功能——而是锁在了官方客户端里。
原因是:Fast mode 的 API 端点对所有人开放,但只有携带正确 cch 签名的请求才会被接受。没有这个签名?API 返回「Fast mode is currently available in research preview in Claude Code. It is not yet available via API.」
这不是服务端的功能开关,而是客户端请求验证。
传统的 API 授权通常长这样:Authorization: Bearer <token>,或者 X-API-Key: <key>。这些 token 是可见的、可提取的、可转移的。你拿到就能用,在任何地方都能用。
Claude Code 的 cch 签名机制把这件事变复杂了。签名算法是公开已知的(xxHash64),但在不知道 seed 的情况下,伪造签名需要用 LLDB 一点点从二进制里挖。在知道 seed 的情况下,伪造签名是可实现的——但 seed 是硬编码在二进制里的。
这意味着:Fast mode 的访问权限从「你知道密钥就能用」,变成了「你需要先逆向这个特定的二进制并提取 seed」。
这不是不可逾越的障碍,但它是真实存在的摩擦。

更重要的是,它的存在本身说明了某种思路的变化:AI 公司的护城河,不只是在模型能力上竞争,还在于把 API 的访问控制从协议层下沉到运行时层。 许可证可以禁止逆向,但许可证无法阻止有人真的去逆向。当源码已经流出,整个机制被公开之后,护城河的价值就只剩下「这个设计很优雅,但实际保护效果有限」的讨论空间了。
安全风险:字符串变异与信任边界
这个机制引入了一个技术层面的安全隐患,值得单独说。
Bun nativeFetch 对 JavaScript 字符串的原地修改,违反了 JS 规范,但在 Bun 的执行环境里这是真实发生的行为。这意味着:
任何在 Bun 运行时里运行的 JavaScript 代码,都不能假设自己创建的字符串在经过 fetch() 调用后还保持原样。
具体风险:
1. 别名引用被污染
const original = JSON.stringify(payload); // "....cch=00000...."const alias = original; // 共享内存await fetch(url, { body: original });// 此刻 original === alias === 被修改后的字符串2. Map/Set 键被静默修改
如果字符串被用作 Map 或 Set 的键,而该字符串恰好包含 cch=00000,fetch() 调用会静默改变其内容,导致键失效或映射关系被破坏。
3. 字符串 intern 扩散
JavaScriptCore 会对短字符串做 intern 处理——同一个值的字符串共享同一个内存对象。如果 fetch() 修改了一个 intern 字符串,所有引用这个字符串的地方都会受影响。
这个问题的影响范围取决于具体场景:在 Claude Code 的实际使用中,用户不太可能直接写出包含特定格式的无关代码。但作为运行时设计的一个特性,它不是无风险的——尤其是当更多应用开始基于 Bun 运行时构建网络密集型产品时。
护城河的深度与局限
Anthropic 在 Claude Code 里埋的这个机制,是一个很有意思的工程案例:它展示了将 API 访问控制从软件层压到运行时层的可能性,也展示了用硬件级别的代码隐蔽性来增加逆向难度的思路。
但它也有明显的局限。
当源码流出后,机制被完整公开,包括 seed、算法、检查逻辑。这把「护城河」从「技术壁垒」降级成了「工程摩擦」。真正的保护效应来自于大部分用户不会去逆向,而不是来自于逆向本身不可行。
这个故事的真正教训也许是:护城河的形式与深度,决定了它的持久性。许可证里的护城河靠法律执行,法律执行靠detection。技术护城河靠逆向难度,但一旦被公开,就只剩下运营层面的壁垒——比如谁有动力用这个信息做坏事。
从这个角度看,Anthropic 的这笔「收购 Oven」的真正价值,或许不在于用运行时锁住 API,而在于拥有了定制 Bun 行为的能力本身。锁 Fast mode 是一个聪明的 demo,但底层的基础设施控制权才是长期有价值的部分。
参考文献
Reverse Engineering Claude Code’s Request Signing (a10k) Bun 官方文档 Anthropic 收购 Oven 公告 Claude Code Fast Mode 文档 xxHash64 规格 mitmproxy