---
title: "Claude Code Source Map 泄露：逆向分析猜对了多少？"
author: deletexiumu
pubDatetime: 2026-04-01T01:00:00+08:00
featured: false
draft: false
tags:
  - Claude Code
  - AI Agent
  - AI 编程
description: "Claude Code npm 包意外附带 source map，约 51 万行还原代码验证了逆向分析的 6 大机制判断，同时揭示了两阶段权限分类器、7 种上下文压缩策略、KAIROS 自主代理等逆向看不到的工程细节。"
---

![封面图](/blog/claude-code-source-map-leak/01-cover.png)

3 月 31 日，Anthropic 往 npm 上推了一版 `@anthropic-ai/claude-code` v2.1.88。包里多了一个 59.8MB 的文件：`cli.js.map`。

这是一个 source map。有了它，任何人都可以把编译打包后的 `cli.js` 还原成原始的 TypeScript 源文件——约 1900 个文件、约 51 万行代码。不是混淆后的 bundle，是可读的开发代码。

安全研究员 Chaofan Shou 美东时间凌晨在海外社区发帖披露，30 分钟内 GitHub 上出现多个还原代码镜像，star 数突破 5000。中文社区 36 氪的标题是"源码泄露，下一个王牌提前曝光"，IT 之家直接写"刚刚被开源"。

这不是第一次。2025 年 2 月，同样的错误已经发生过一次，当时 Anthropic 从 npm 撤包处理。同一个 source map 文件，同一个 .npmignore 遗漏，13 个月后再次发生。

上一篇文章[《两天重建 Claude Code：一万行代码背后的架构设计》](/posts/rebuild-claude-code-architecture/)我们从混淆代码逆向推理了 Claude Code 的 6 大机制。现在还原代码摆在面前——**逆向猜对了多少？漏掉了什么？还原代码中还有哪些逆向根本看不到的东西？**

**声明**：本文分析的不是 Anthropic 官方仓库，而是基于 `cli.js.map` 还原的代码镜像。还原代码中有部分模块被编译期 DCE（Dead Code Elimination）移除，部分代码为 stub。所有结论仅代表该版本快照，不等于 Anthropic 内部完整代码库。本文不提供获取方式、不复现大段专有代码，只讨论公开流传事实与架构启示。

---

## 第一部分：6 大机制验证总览

先快速对照序号 46 的逆向分析与还原代码的实际情况。

**Agent Loop** — 全部印证。while(true) + fork runner + maxTurns 的核心结构完全正确。修正：stop_reason 不可靠，实际用 needsFollowUp 标志驱动循环；工具支持并发执行（默认上限 10）。

**Prompt Caching** — 全部印证。动态边界分割和三层缓存区域准确。修正：新增 global 跨组织缓存作用域；缓存命中率监控系统追踪 13+ 变量。

**工具系统** — 大部分印证。修正：逆向推断的六阶段中，renderToolCall 是 UI 渲染不在执行管道内，checkpoint 合并到执行阶段内部，实际核心五阶段（输入验证→Pre-Hook→权限→执行→Post-Hook）；shouldDefer 是静态属性，非衰减算法。

**权限系统** — 全部印证。四种模式、三层决策、reasoning-blind 均正确。修正：分类器是两阶段 XML 管道（64 tokens 快判 + 4096 tokens 链式推理）；有 denial 熔断机制。

**Context 管理** — 核心逻辑印证。修正：实际存在约 7 种并行压缩策略（大部分为内部实验功能）；compact 有熔断器。

**多 Agent** — 全部印证。name@team + worktree + 后台生命周期均正确。修正：三种后端（tmux/iTerm2/in-process）；双层消息系统；Coordinator Mode 独立代码路径。

**小结**：6 个机制的核心架构判断基本成立，修正集中在实现细节——阶段数量、算法机制、内部实验功能。逆向能看到骨架，但还原代码揭示了骨架上的每一条肌肉和神经。

下面只深挖**变化最大的 3 个机制**。

---

## 深挖一：权限系统——两阶段分类器的完整面貌

序号 46 描述了 Auto Mode 的三层决策架构，也提到了"单 token 快速过滤 + 链式推理"。但分类器内部具体怎么工作，逆向只看到了轮廓。还原代码给出了完整画面。

### 两阶段 XML 管道

分类器在 `yoloClassifier.ts` 中实现，采用两阶段流水线：

**Stage 1（快速判决）**：`max_tokens: 64`，prompt 末尾附加一句 suffix：

```typescript
const XML_S1_SUFFIX = '\nErr on the side of blocking. <block> immediately.'
```

模型输出 `<block>no</block>` 直接放行，输出 `<block>yes</block>` 才升级到 Stage 2。设计意图很明确——Stage 1 倾向于"宁可多拦"，误拦可以被 Stage 2 纠正，漏放则无法追回。

**Stage 2（链式推理）**：`max_tokens: 4096`，suffix 要求模型先用 `<thinking>` 输出思维链，再给出 `<block>` 判决：

```typescript
const XML_S2_SUFFIX =
  '\nReview the classification process and follow it carefully, making sure you deny actions that should be blocked. As a reminder, explicit (not suggestive or implicit) user confirmation is required to override blocks. Use <thinking> before responding with <block>.'
```

两阶段共享几乎相同的 prompt 前缀，区别只在末尾的 suffix。这意味着 Stage 1 的 Prompt Cache 自动被 Stage 2 复用——安全检查本身也在享受缓存红利。

还原代码还揭示了第三种运行模式：除了默认的 `'both'`（先 Stage 1 再 Stage 2），还有 `'fast'`（只跑 Stage 1，但把 max_tokens 提到 256 并去掉 stop_sequences）和 `'thinking'`（跳过 Stage 1 直接进 Stage 2）。不同模式是安全与速度的梯度选择。

### 三重防注入设计

这是逆向只模糊感知到"reasoning-blind"，但还原代码展示了完整的三层防线：

**第一层：推理盲**。`buildTranscriptEntries()` 在构建分类器输入时，assistant 消息只保留 `tool_use` 块，text 块全部丢弃：

```typescript
// Only include tool_use blocks — assistant text is model-authored
// and could be crafted to influence the classifier's decision.
if (block.type === 'tool_use') {
  blocks.push({ type: 'tool_use', name: block.name, input: block.input })
}
```

分类器看不到模型说了什么，只看到模型做了什么。这阻断了"模型在推理中自我说服以绕过权限"的攻击路径。

**第二层：Thinking 剥离**。`stripThinking()` 在解析 XML 前用正则删除所有 `<thinking>` 标签内容，防止模型在思维链中注入伪造的 `<block>no</block>`。

**第三层：Iron Gate**。分类器不可用时默认 fail-closed（拒绝而非放行），通过远程 flag 实时控制。

### 熔断与降级

还原代码中还有一个逆向完全看不到的机制：`denialTracking.ts` 实现了分类器的熔断降级。

```typescript
export const DENIAL_LIMITS = {
  maxConsecutive: 3,
  maxTotal: 20,
} as const

export function shouldFallbackToPrompting(state: DenialTrackingState): boolean {
  return (
    state.consecutiveDenials >= DENIAL_LIMITS.maxConsecutive ||
    state.totalDenials >= DENIAL_LIMITS.maxTotal
  )
}
```

连续被拒绝 3 次或会话内累计 20 次后，自动降级为手动审批模式。成功的工具调用会重置连续拒绝计数器。这是一个实用的安全网：防止分类器误判导致用户永远卡在"需要确认"的循环里。

**对用户的意义**：Auto Mode 下某些操作秒过（规则表直接 allow 或 acceptEdits 快速路径），某些等半秒（进了两阶段分类器）。如果某个操作反复需要确认，在 `.claude/settings.json` 加 allow 规则让它走第一层。但别加 `Bash(*)`——进入 Auto Mode 时会被自动剥除。

---

## 深挖二：Context 管理——不止一种压缩

序号 46 只描述了 auto-compact 的"摘要→恢复→替换"三步走。还原代码中出现了至少 7 种上下文管理策略——虽然大部分是内部实验功能（被 DCE 移除或 feature flag 关着），但它们的存在本身说明了 Anthropic 在这个问题上投入的工程深度。

### 外部用户可见的策略

**Auto-compact**：传统的摘要压缩。触发阈值在 `autoCompact.ts` 中定义：

```typescript
export const AUTOCOMPACT_BUFFER_TOKENS = 13_000
// Stop trying autocompact after this many consecutive failures.
// BQ 2026-03-10: 1,279 sessions had 50+ consecutive failures (up to 3,272)
// in a single session, wasting ~250K API calls/day globally.
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3
```

有效上下文窗口减去 13000 tokens 时触发。注释暴露了一个真实的工程故障：曾有 1279 个会话陷入连续压缩失败的死循环，每天全局浪费约 25 万次 API 调用——所以加了 3 次连续失败就停止重试的熔断器。

**API Microcompact**：利用 Anthropic API 的服务端指令（`clear_tool_uses`、`clear_thinking`）在服务端清理旧内容，客户端消息不变。这比客户端压缩更轻量，不需要额外的 API 调用。

**Time-Based Microcompact**：当距上次交互超过 60 分钟时（1 小时缓存 TTL 已失效），主动清理旧 tool_result 以缩小重建缓存的开销。这是一个专门针对"放了很久再回来继续"场景的优化。

### 内部实验策略（代码痕迹可见但功能未开放）

还原代码中还有 4 种未开放的策略：

- **Session Memory Compact**：利用持续更新的 session_memory 文件作为摘要来源，省掉一次 API 调用
- **Reactive Compact**：不主动压缩，等 API 返回 413 错误后再压缩重试——"玻璃破碎时才用"的兜底方案
- **Context Collapse**：90% 使用率时"提交"上下文，95% 时阻止新 agent spawn
- **Snip Module**：对话历史精确裁剪，暴露为 SnipTool 供模型主动调用

这些代码在外部构建中被移除，现有用户无法使用。但它们说明了一件事：Anthropic 不满足于"只有一种压缩方式"，正在探索从被动兜底到主动裁剪的完整策略光谱。

### Compact 的质量保证细节

还原代码中最让人意外的是 compact 摘要 prompt 的精细度。`prompt.ts` 中定义了 9 段结构化摘要模板：

```typescript
const BASE_COMPACT_PROMPT = `Your task is to create a detailed summary...
// 9 段结构化模板，其中两条最关键：
6. All user messages: List ALL user messages that are not tool results.
// ...
9. Optional Next Step: ...IMPORTANT: ensure that this step is DIRECTLY
   in line with the user's most recent explicit requests...`
```

第 6 条"列出所有用户消息"确保用户的原始意图不被摘要丢弃。第 9 条"下一步必须与用户最近请求直接对齐"防止模型在压缩后"走偏"去做自己想做的事。其余 7 条覆盖技术概念、文件清单、错误修复、待办事项等维度，构成完整的结构化摘要框架。

模型先输出 `<analysis>` 思维草稿，草稿在注入上下文前被剥离——只保留精炼摘要。如果摘要请求本身也超限（prompt-too-long），会从头部截断旧对话并重试，最多 3 次。

**对用户的意义**：长对话中 Claude Code "忘记"之前的内容是 compact 的信息损失，不是 bug。应对策略不变——关键信息写进文件（plan、CLAUDE.md、memory），compact 后会自动恢复。但现在你知道 compact 背后有多层保底和质量控制，不只是简单的"做个摘要然后祈祷"。

---

## 深挖三：工具并发模型与 Deferred Tools 的真实机制

这里有一个序号 46 的明确错误需要修正。

### Deferred Tools：不是衰减算法

序号 46 说 Deferred Tools 用"7 天半衰期的使用频率衰减算法"判断工具是否该被延迟加载。还原代码显示这是错的——`shouldDefer` 是工具定义时的**静态属性**，一个工具要么始终 deferred，要么始终 loaded，跟使用频率无关。

实际机制很直接：

- 约 24 个工具被硬编码为 `shouldDefer: true`，包括 TodoWrite、所有 Task/Team 工具、Worktree 工具、WebSearch/WebFetch 等
- MCP 工具默认全部 deferred（除非设置了 `alwaysLoad`）
- ToolSearch 本身永远不 deferred——模型需要它来加载其他工具

ToolSearch 的匹配逻辑也不复杂：工具名精确匹配 +10 分、searchHint 匹配 +4 分、description 匹配 +2 分。按分数排序返回。

回头看，逆向时看到的"衰减算法"可能是混淆代码中其他模块的逻辑被错误关联到了 Deferred Tools 上。这提醒了一件事：混淆代码中变量名和函数边界被打乱，跨模块的逻辑归属很容易判断错误。

### 工具并发执行（逆向完全没看到的）

还原代码揭示了一个序号 46 完全遗漏的机制：同一轮返回的多个工具调用可以并行执行。

`partitionToolCalls()` 函数把同一轮的工具调用分成并发安全组和串行组。判断标准是 `isConcurrencySafe()` 属性——Read、Grep、Glob 等只读工具返回 true，Edit、Write、Bash 等写操作工具返回 false。

并发上限由环境变量 `CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY` 控制，默认 10。也就是说，当模型同时请求读取 5 个文件时，这 5 个 Read 操作是并行的。但如果模型要连续编辑 3 个文件，这 3 个 Edit 操作严格串行，确保文件系统一致性。

这解释了一个日常使用中的体感差异：让 Claude Code 一次读多个文件时感觉很快，但连续编辑多个文件时明显变慢——前者是并发执行，后者是排队等待。

**对用户的意义**：同时让 Claude Code 读 5 个文件比逐个读快很多。但 Edit/Write 必须排队。如果觉得 ToolSearch 偶尔慢一拍，可能是你想用的工具被 deferred 了——模型需要先调一次 ToolSearch 才能拿到该工具的 schema。

---

## 第二部分：逆向看不到的——还原代码中的实验痕迹

以下内容在混淆代码中被 Dead Code Elimination 完全移除或被 feature flag 隐藏，逆向工程无法触及。这些是代码痕迹和实验功能，不代表 Anthropic 已确认的产品路线图。

### KAIROS：自主代理模式的实验痕迹

还原代码中出现了 6 个以 KAIROS 命名的 feature flag，是所有隐藏功能中规模最大的。代码痕迹显示，这是一个"持续待机"的自主代理模式：

```typescript
return `# Autonomous work

You are running autonomously. You will receive \`<tick>\` prompts
that keep you alive between turns — just treat them as
"you're awake, what now?"

## Terminal focus
- **Unfocused**: The user is away. Lean heavily into autonomous action
  — make decisions, explore, commit, push.
- **Focused**: The user is watching. Be more collaborative
  — surface choices, ask before committing to large changes...`
```

系统通过 `<tick>` 心跳保持代理存活，无事可做时调用 `SleepTool` 暂停。关键设计在于终端焦点感知：用户离开时（unfocused）代理自主行动——探索、测试、提交、推送；用户在看时（focused）切换为协作模式——展示选择、征求同意。

配套工具包括 `BriefTool`（主动状态报告）、`PushNotificationTool`（推送到手机）、`SubscribePRTool`（订阅 PR 事件）。这些工具在 `tools.ts` 中的注册全部被 KAIROS flag 门控：

```typescript
const SleepTool = feature('PROACTIVE') || feature('KAIROS')
  ? require('./tools/SleepTool/SleepTool.js').SleepTool
  : null
const PushNotificationTool =
  feature('KAIROS') || feature('KAIROS_PUSH_NOTIFICATION')
    ? require('./tools/PushNotificationTool/PushNotificationTool.js')
        .PushNotificationTool
    : null
```

Bun 的 `feature()` 宏在编译时直接进行死代码消除——外部构建中这些 require 变成 `null`，不会出现在最终 bundle 里。

这些代码在外部构建中被完全移除，现有用户无法使用。是否会正式发布、以什么形式发布，目前没有官方信息。

### Feature Flag 与远程管控

还原代码中可识别出约 80 个编译期 feature flag，其中约 20 个控制尚未发布的功能（VOICE_MODE、WEB_BROWSER_TOOL 等）。flag 名称使用随机词对混淆（如 `tengu_cobalt_raccoon`），从名称无法推断用途。

更值得关注的是远程管控机制。`remoteManagedSettings` 模块每小时轮询 `/api/claude_code/settings`，可远程推送安全策略。代码中有一个"接受或退出"的硬设计：

```typescript
export function handleSecurityCheckResult(result: SecurityCheckResult): boolean {
  if (result === 'rejected') {
    gracefulShutdownSync(1);
    return false;
  }
  return true;
}
```

用户拒绝安全相关的远程设置变更时，应用以退出码 1 终止，没有"拒绝并继续使用"的选项。这是企业合规的强制机制——安全策略不是可选的，要么接受新策略，要么不用这个工具。

### 彩蛋：Buddy 虚拟宠物

还原代码中还有一个完整的虚拟宠物模块 Buddy：18 个物种、5 种稀有度（common 60%、uncommon 25%、rare 10%、epic 4%、legendary 1%），用 Mulberry32 伪随机数生成器基于用户 ID 哈希确定性生成——同一个用户永远孵化出同一只宠物。

上线日期硬编码在代码中：

```typescript
export function isBuddyTeaserWindow(): boolean {
  const d = new Date();
  return d.getFullYear() === 2026 && d.getMonth() === 3 && d.getDate() <= 7;
}
```

2026 年 4 月 1 日至 7 日的愚人节窗口——如果你现在打开 Claude Code，可能已经能看到这只宠物了。使用本地时间而非 UTC，代码注释解释了原因——让热度在 24 小时内跨时区滚动传播，而不是在 UTC 午夜集中爆发。一个虚拟宠物彩蛋，连发布时间的传播动力学都考虑到了。

有趣的细节：物种名用 `String.fromCharCode` 编码而不是直接写字符串，因为有一个物种名和 Anthropic 内部模型代号冲突，会被 `excluded-strings.txt` 的构建扫描拦截。运行时构造字符串可以绕过文本扫描。

---

## 逆向分析的准确性说明了什么

序号 46 的逆向分析在 6 大机制的核心判断上基本成立，修正主要集中在实现细节。这说明好的架构设计是"可推理的"——即使只看混淆代码，核心逻辑也足够清晰到能被正确还原。while 循环就是 while 循环，缓存分段就是缓存分段，三层权限就是三层权限。混淆能隐藏变量名，隐藏不了结构。

但还原代码揭示的精细度远超逆向能力范围：多种压缩策略的共存、分类器的两阶段 XML 管道及三重防注入、工具并发执行模型、大量 feature flag 的编排——这些是逆向无法触及的工程复杂性。Deferred Tools 的"衰减算法"被证伪也说明了混淆代码逆向的固有风险：跨模块的逻辑归属很容易判断错误。

对于普通用户，这次事件不改变任何使用建议。还原出的是客户端代码，目前没有证据显示服务端密钥或用户数据受影响。

**一个独立的安全提醒**：与源码泄露同日（3 月 31 日），npm 上的 `axios` 包遭遇了独立的供应链攻击事件。如果你在当天通过 npm 更新了 Claude Code 或其他项目依赖，建议检查 lockfile 中是否引入了异常版本。这与 Claude Code 源码泄露无关，但时间上的巧合值得警惕。

落到日常使用上：Auto Mode 下反复需要确认的操作，在 `.claude/settings.json` 加精确的 allow 规则（如 `Bash(npm test)`）让它走规则表直接通过，不进分类器。长对话的关键信息写进 plan 文件或 CLAUDE.md，不要只在对话中提及——compact 有 9 段结构化模板和 3 次失败熔断保底，但信息损失不可避免。需要读多个文件时一次性请求，Read 是并发安全的，比逐个读快；写操作无法并发，这是设计约束。ToolSearch 偶尔慢一拍，是因为目标工具被 deferred 了，模型需要额外一轮查询拿到 schema，MCP 工具默认全部 deferred。

---

## 相关阅读

- [两天重建 Claude Code：一万行代码背后的架构设计](/posts/rebuild-claude-code-architecture/) — 本文的前篇，从混淆代码逆向推理 Claude Code 6 大机制
- [Claude Code 安全三道防线：从权限模式到 Hook 兜底的纵深防护实战](/posts/claude-code-safety-three-defenses/) — 权限系统的用户视角实战指南，与本文的分类器深挖互补
- [从一条推文读懂 Claude Code：工具设计原理与实战指南](/posts/claude-code-tool-design-philosophy/) — 工具系统的设计哲学，与本文的工具并发模型和 Deferred Tools 修正互补
