Claude Code 源码拆解:流式工具执行 + 并发安全标记
深入 Anthropic Claude Code 源码,拆解一个让 AI Agent 快一倍的工程设计。
前言
2026 年 3 月 31 日,Anthropic 的 Claude Code CLI 工具因 npm 包中的 source map 暴露,其完整 TypeScript 源码被公开。这套代码体量惊人——1,900 个文件、51 万行代码——堪称目前公开可见的最复杂的 AI Agent 工程实现。
我花了一些时间研读,发现其中有一个设计堪称”教科书级别”——流式工具执行(Streaming Tool Execution)+ 并发安全标记(Concurrency Safety Marking)。
这个设计解决了一个所有 Agent 框架都面临的核心性能问题:模型在思考时,CPU 在空等。
一、先搞清楚问题:传统 Agent 的执行时序
大多数 Agent 框架(包括 LangChain、AutoGPT 等)的工具调用流程是这样的:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 调用 LLM │───▶│ 等待输出完成 │───▶│ 执行工具 │───▶│ 再调 LLM │
│ (2-10s) │ │ (空等) │ │ (0.5-5s) │ │ (2-10s) │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
这里有两个浪费:
-
1. LLM 输出是流式的,第一个 tool_use块可能在第 2 秒就完整了,但你得等到第 8 秒所有输出结束才开始执行 -
2. 多个工具是串行的,即使”读文件 A”和”读文件 B”完全独立,也是一个做完再做下一个
Claude Code 对这两个问题的回答是:不等,边收边执行;能并行的就并行,不能的就串行。
二、核心设计一:流式工具执行
关键代码:模型还在说话,工具已经在跑了
在 Claude Code 的核心查询循环 query.ts 中,当 LLM 的流式响应到达时:
// query.ts — 模型流式输出的处理循环
if (message.type === 'assistant') {
assistantMessages.push(message)
const msgToolUseBlocks = message.message.content.filter(
content => content.type === 'tool_use',
) asToolUseBlock[]
if (msgToolUseBlocks.length > 0) {
toolUseBlocks.push(...msgToolUseBlocks)
needsFollowUp = true
}
// 关键:模型还在流式输出,工具已经开始执行
if (streamingToolExecutor) {
for (const toolBlock of msgToolUseBlocks) {
streamingToolExecutor.addTool(toolBlock, message) // 立即调度!
}
}
}
// 同时收割已完成的结果
if (streamingToolExecutor) {
for (const result of streamingToolExecutor.getCompletedResults()) {
yield result.message// 工具结果立即输出
}
}
注意这里的时序:
传统方式:
LLM输出: ████████████████████████████████
工具执行: ▓▓▓▓▓▓▓▓▓▓▓▓▓▓
总耗时: ════════════════════════════════════════════════
Claude Code:
LLM输出: ████████████████████████████████
工具1: ▓▓▓▓▓▓ ← 第一个 tool_use 解析完就开跑
工具2: ▓▓▓▓ ← 第二个紧跟着
工具3: ▓▓▓▓▓▓▓▓ ← 第三个也是
总耗时: ════════════════════════════════ ← 节省了整个工具执行时间!
StreamingToolExecutor 的状态机
每个工具在执行器中经历四个状态:
typeToolStatus = 'queued' | 'executing' | 'completed' | 'yielded'
queued → executing → completed → yielded
│ │
└── 被取消(abort)────┘ → 生成合成错误消息
addTool() 将工具入队后立即触发 processQueue(),调度逻辑检查当前并发状态后决定是立刻执行还是等待。
三、核心设计二:并发安全标记
流式执行引入了一个新问题:哪些工具可以并行跑?
“读文件”和”搜索代码”可以同时跑,但”写文件 A”和”删除文件 A”绝不能同时跑。Claude Code 的方案是让每个工具自己声明是否并发安全。
每个工具的自我声明
// GlobTool — 文件搜索,纯读取,天然并发安全
isConcurrencySafe() {
returntrue
},
// GrepTool — 内容搜索,纯读取,并发安全
isConcurrencySafe() {
returntrue
},
// FileReadTool — 读文件,并发安全
isConcurrencySafe() {
returntrue
},
// WebFetchTool — 抓网页,并发安全
isConcurrencySafe() {
returntrue
},
// AgentTool — 子代理,并发安全(子代理内部自己管理)
isConcurrencySafe() {
returntrue
},
// BashTool — 关键!根据命令内容动态判断
isConcurrencySafe(input) {
returnthis.isReadOnly?.(input) ?? false
// ls, cat, grep → true(只读命令可并行)
// rm, mv, pip install → false(写命令必须独占)
},
// McpAuthTool — 认证操作,不能并发
isConcurrencySafe: () =>false,
注意 BashTool 的设计——它不是简单地返回 true/false,而是解析命令内容后动态判定。ls && cat foo.txt 是只读的,可以并行;mkdir build && npm run build 有写操作,必须独占。
下面我们深入展开这个判定过程。
三(续)、深入:BashTool 如何根据命令内容判断并发安全性
BashTool 的 isConcurrencySafe 只有一行代码,但它背后是一个五层纵深防御的只读判定体系。
第一层:入口——isConcurrencySafe 委托给 isReadOnly
// BashTool.tsx
isConcurrencySafe(input) {
returnthis.isReadOnly?.(input) ?? false// 并发安全 = 只读
},
isReadOnly(input) {
const compoundCommandHasCd = commandHasAnyCd(input.command)
const result = checkReadOnlyConstraints(input, compoundCommandHasCd)
return result.behavior === 'allow'
},
核心逻辑是:如果命令是只读的,那它就是并发安全的。这个等式很直觉——只读操作不修改状态,多个同时跑也不会互相干扰。
第二层:复合命令拆分与安全预检
checkReadOnlyConstraints 是整个判定的总调度器。它拿到原始命令后,先做一系列安全预检:
exportfunctioncheckReadOnlyConstraints(input, compoundCommandHasCd) {
// 1. 命令能不能被解析?不能解析 → 无法判断 → 不允许并发
const result = tryParseShellCommand(command)
if (!result.success) return { behavior: 'passthrough' }
// 2. 通用安全检查(命令替换、变量注入等)
if (bashCommandIsSafe_DEPRECATED(command).behavior !== 'passthrough')
return { behavior: 'passthrough' }
// 3. Windows UNC 路径检查(防止 WebDAV 凭证泄露)
if (containsVulnerableUncPath(command))
return { behavior: 'ask' }
// 4. cd + git 组合检查(防止沙箱逃逸)
if (compoundCommandHasCd && hasGitCommand)
return { behavior: 'passthrough' }
// 5. 裸 git 仓库检查(防止恶意 hooks 执行)
if (hasGitCommand && isCurrentDirectoryBareGitRepo())
return { behavior: 'passthrough' }
// 6. 写 git 内部路径 + 执行 git(防止构造恶意仓库后触发 hooks)
if (hasGitCommand && commandWritesToGitInternalPaths(command))
return { behavior: 'passthrough' }
// 7. 最终:拆分复合命令,逐个子命令验证
const allSubcommandsReadOnly = splitCommand(command)
.every(subcmd =>isCommandReadOnly(subcmd))
if (allSubcommandsReadOnly) return { behavior: 'allow' }
return { behavior: 'passthrough' }
}
注意第 4-6 步的 git 特殊防御:攻击者可以构造一个命令如 mkdir -p hooks && echo 'malicious' > hooks/pre-commit && git status,先创建恶意 git hooks 文件,再触发 git 来执行它们。这种组合攻击会被专门拦截。
第三层:单命令只读判定——双轨验证
每个子命令要过两道关卡:
关卡 A:基于 flag 解析的命令白名单(isCommandSafeViaFlagParsing)
这是最精密的一层。Claude Code 维护了一个巨大的命令安全配置表,精确到每个 flag 级别:
constCOMMAND_ALLOWLIST = {
grep: {
safeFlags: {
'-i': 'none', // 忽略大小写,安全
'-r': 'none', // 递归搜索,安全
'-n': 'none', // 显示行号,安全
'-c': 'none', // 只显示计数,安全
'-A': 'number', // 后文行数,参数必须是数字
'-B': 'number', // 前文行数,参数必须是数字
'-e': 'string', // 指定模式,安全
// ... 50+ 个 flag 逐一声明
},
},
sed: {
safeFlags: {
'-n': 'none', // 静默模式,安全
'-E': 'none', // 扩展正则,安全
// 注意:-i(原地修改)不在列表里 → 被排除 → 不是只读
},
// 额外的自定义校验回调
additionalCommandIsDangerousCallback: (rawCommand, args) =>
!sedCommandIsAllowedByAllowlist(rawCommand),
},
'git diff': { // 支持多词命令
safeFlags: { /* ... */ },
},
'git log': {
safeFlags: { /* ... */ },
},
// ... 几十个命令的完整配置
}
Flag 的参数类型也有严格约束:
typeFlagArgType =
| 'none'// 无参数(--color, -n)
| 'number'// 必须是整数(--context=3)
| 'string'// 任意字符串
| 'char'// 单字符(分隔符)
| '{}'// 只能是字面量 "{}"
| 'EOF'// 只能是字面量 "EOF"
不在白名单里的 flag 一律拒绝。比如 sed -i 的 -i 不在 safeFlags 中,因为它会原地修改文件 → 不是只读 → 不允许并发。
关卡 B:正则匹配的简单只读命令
对于不需要复杂 flag 分析的简单命令,用正则快速匹配:
constREADONLY_COMMANDS = [
'cat', 'head', 'tail', 'wc', 'stat', 'strings', // 文件查看
'id', 'uname', 'free', 'df', 'du', // 系统信息
'basename', 'dirname', 'realpath', // 路径操作
'cut', 'paste', 'tr', 'column', 'sort', // 文本处理
'diff', 'comm', 'cmp', // 文件比较
'which', 'type', 'sleep', 'true', 'false', // 杂项安全命令
// ... 50+ 个命令
]
每个命令被转化为安全正则(makeRegexForSafeCommand),确保不含 Shell 元字符:
functionmakeRegexForSafeCommand(command: string): RegExp {
// 匹配:命令名 + 不含危险字符的参数
// 阻止:管道 |、重定向 <>、命令替换 $()``、花括号展开 {} 等
returnnewRegExp(`^${command}(?:\\s|$)[^<>()$\`|{}&;\\n\\r]*$`)
}
第四层:变量展开和 Glob 防护
在进入命令白名单匹配之前,还有一道关键检查——阻止运行时才能确定的内容:
// 阻止未引用的 $ 变量展开
// 攻击:uniq --skip-chars=0$_ → $_ 运行时展开为上一个命令的参数
// 通过 IFS 分词,可以偷渡额外的位置参数,绕过"只有 flag"的正则
// 阻止未引用的 Glob 字符
// 攻击:python * → 如果目录下有个叫 --help 的文件
// * 会展开为 python --help,改变命令行为
functioncontainsUnquotedExpansion(command: string): boolean {
let inSingleQuote = false
let inDoubleQuote = false
for (let i = 0; i < command.length; i++) {
// 追踪引号状态...
// 单引号内一切都是字面量,跳过
if (inSingleQuote) continue
// $ 在双引号和非引用位置都会展开
if (currentChar === '$') {
const next = command[i + 1]
if (next && /[A-Za-z_@*#?!$0-9-]/.test(next)) returntrue
}
// Glob 只在非引用位置展开
if (!inDoubleQuote && /[?*[\]]/.test(currentChar)) returntrue
}
returnfalse
}
精彩之处在于引号状态追踪——grep '$HOME'(单引号)中的 $ 是安全的字面量,但 grep "$HOME"(双引号)中的 $ 会被 Shell 展开,不安全。
第五层:特定命令的自定义校验回调
有些命令的危险性不能只靠 flag 判断,需要语义级别的检查:
// ps 命令:BSD 风格的 'e' 参数会泄露环境变量
ps: {
safeFlags: { '-e': 'none', '-f': 'none', /* ... */ },
additionalCommandIsDangerousCallback: (rawCommand, args) => {
// 检测 BSD 风格选项中是否包含 'e'
// "ps aux" 安全,但 "ps auxe" 不安全(e 泄露所有进程的环境变量)
return args.some(
a => !a.startsWith('-') && /^[a-zA-Z]*e[a-zA-Z]*$/.test(a),
)
},
},
// date 命令:位置参数可能设置系统时间
date: {
safeFlags: { '-d': 'string', '-u': 'none', /* ... */ },
additionalCommandIsDangerousCallback: (rawCommand, args) => {
// 位置参数必须以 + 开头(格式字符串如 +"%Y-%m-%d")
// 不以 + 开头的位置参数(如 MMDDhhmm)是在设置系统时间!
// ... 逐 token 分析 ...
},
},
// tput 命令:某些终端能力值可以执行代码
tput: {
safeFlags: { '-T': 'string', '-V': 'none' },
additionalCommandIsDangerousCallback: (rawCommand, args) => {
constDANGEROUS_CAPABILITIES = newSet([
'init', 'reset', // 执行 iprog(terminfo 中的任意代码)
'clear', // 清除回滚缓冲(证据销毁)
'mc5', 'mc5p', // 激活媒体复制(重定向到打印机)
'pfloc', // 本地执行字符串
// ...
])
return args.some(token =>DANGEROUS_CAPABILITIES.has(token))
},
},
完整判定流程图
输入: "git diff HEAD~3 && grep -r 'TODO' src/"
│
▼
┌──────────────────────────────┐
│ Shell 解析器能解析吗? │ ── 不能 → 不是只读 → 不可并发
└──────────┬───────────────────┘
│ 能
▼
┌──────────────────────────────┐
│ 通用安全检查 │
│ (命令替换? 变量注入? UNC?) │ ── 有风险 → 不是只读
└──────────┬───────────────────┘
│ 通过
▼
┌──────────────────────────────┐
│ Git 特殊防御 │
│ (cd+git? 裸仓库? 写hooks?) │ ── 命中 → 不是只读
└──────────┬───────────────────┘
│ 通过
▼
┌──────────────────────────────┐
│ 拆分复合命令 │
│ "git diff HEAD~3" │
│ "grep -r 'TODO' src/" │
└──────────┬───────────────────┘
│
┌─────┴─────┐
▼ ▼
子命令1 子命令2
│ │
▼ ▼
┌─────────┐ ┌─────────┐
│ flag解析 │ │ flag解析 │
│白名单校验│ │白名单校验│ ← 每个 flag 逐一比对安全配置表
└────┬────┘ └────┬────┘
│ 通过 │ 通过
▼ ▼
┌─────────┐ ┌─────────┐
│自定义回调│ │自定义回调│ ← 语义级别的额外检查
└────┬────┘ └────┬────┘
│ 通过 │ 通过
▼ ▼
✅ 只读 ✅ 只读
全部子命令都是只读 → ✅ isConcurrencySafe = true → 可以并行!
为什么这个设计值得学?
1. 白名单 > 黑名单
不是”列出哪些命令危险”,而是”列出哪些命令安全”。不在白名单里的一律当作不安全——这意味着新出现的未知命令自动被保护。
2. flag 级别的精度
sed -n 'p'(只打印)vs sed -i 's/foo/bar/'(原地修改),同一个命令因为 flag 不同,安全性完全不同。精确到 flag + 参数类型的白名单,是这个系统能在保证安全的同时最大化并发的关键。
3. 防御纵深(Defense in Depth)
不是只有一道关卡,而是五层层层递进。即使某一层被绕过(比如 Shell 解析的边界 case),后面的层还能拦住。tput 的 -S flag 就是一个典型——validateFlags 会拦它,additionalCommandIsDangerousCallback 也会拦它,双保险。
4. 解析器一致性防护
Claude Code 发现了一个微妙的安全问题:Shell 解析库(shell-quote)和真实的 Bash 对同一个命令的解析可能不一致。比如 $VAR 在解析库中是字面量,但在 Bash 中会展开。所以它统一拒绝所有包含 $ 的 token,从根源消除解析差异攻击面。
代码中甚至有这样的注释:
// SECURITY: `$Z--output=/tmp/pwned` 在解析器中以 `$` 开头 → 被当作位置参数
// 但 Bash 运行时 $Z 为空 → 变成 `--output=/tmp/pwned` → 任意文件写入
这种对”解析器差异 = 攻击面”的深刻理解,是真正在生产环境被打过之后才会有的防御意识。
默认值:fail-closed
// Tool.ts — buildTool 的默认值
constTOOL_DEFAULTS = {
isConcurrencySafe: (_input?: unknown) =>false, // 默认不允许并发
// ...
}
如果工具开发者忘了声明,默认就是不允许并发。这是安全工程中的经典原则——默认关闭(fail-closed),宁可慢一点,也不出并发 bug。
四、调度算法:分区批处理
有了每个工具的并发安全标记,调度器如何编排执行顺序?
partitionToolCalls:把工具调用分成批次
functionpartitionToolCalls(toolUseMessages, toolUseContext): Batch[] {
return toolUseMessages.reduce((acc, toolUse) => {
const tool = findToolByName(toolUseContext.options.tools, toolUse.name)
const isConcurrencySafe = tool?.isConcurrencySafe(parsedInput.data)
// 连续的并发安全工具合并到同一个批次
if (isConcurrencySafe && acc[acc.length - 1]?.isConcurrencySafe) {
acc[acc.length - 1].blocks.push(toolUse)
} else {
// 非安全工具或新的批次
acc.push({ isConcurrencySafe, blocks: [toolUse] })
}
return acc
}, [])
}
假设模型输出了这些工具调用:
1. GrepTool("搜索 API 定义") ← 并发安全
2. FileReadTool("读 config.ts") ← 并发安全
3. FileReadTool("读 types.ts") ← 并发安全
4. BashTool("npm install lodash") ← 非并发安全
5. FileEditTool("修改 config.ts") ← 非并发安全
6. GrepTool("验证修改结果") ← 并发安全
分区结果:
批次 1 [并行]: GrepTool + FileReadTool + FileReadTool ← 三个同时跑
批次 2 [串行]: BashTool("npm install") ← 独占执行
批次 3 [串行]: FileEditTool("修改文件") ← 独占执行
批次 4 [并行]: GrepTool("验证") ← 独占(只有一个)
StreamingToolExecutor 的并发控制
流式执行器用更精细的逻辑控制准入:
privatecanExecuteTool(isConcurrencySafe: boolean): boolean {
const executingTools = this.tools.filter(t => t.status === 'executing')
return (
executingTools.length === 0 || // 没有正在执行的 → 直接跑
(isConcurrencySafe && executingTools.every(t => t.isConcurrencySafe))
// 自己安全 && 正在跑的也都安全 → 可以一起跑
)
}
用一句话概括这个规则:安全的工具可以和其他安全的工具并行;不安全的工具必须独占。
五、错误传播:兄弟取消机制
并行执行引入了另一个问题:如果三个 Bash 命令并行跑,第一个失败了,剩下两个怎么办?
Claude Code 的答案是兄弟取消(Sibling Cancellation),但只对 Bash 工具生效:
// StreamingToolExecutor.ts — 执行过程中检测错误
if (isErrorResult) {
thisToolErrored = true
// 只有 Bash 错误才取消兄弟。
// Bash 命令常有隐式依赖链(mkdir 失败 → 后续命令没意义)。
// Read/WebFetch 等是独立的——一个失败不应该影响其他。
if (tool.block.name === BASH_TOOL_NAME) {
this.hasErrored = true
this.erroredToolDescription = this.getToolDescription(tool)
this.siblingAbortController.abort('sibling_error')
}
}
为什么只对 Bash 做兄弟取消?因为 Shell 命令之间常有隐式依赖(mkdir build 失败了,后面的 cp file build/ 必然也会失败),而 FileReadTool 读两个不同文件是完全独立的,一个 404 不影响另一个。
被取消的工具收到的是合成错误消息,不是静默吞掉:
// 生成给模型看的取消原因
const msg = desc
? `Cancelled: parallel tool call ${desc} errored`
: 'Cancelled: parallel tool call errored'
这样模型知道”不是工具本身出错,而是因为兄弟命令失败被连带取消了”,可以做出更好的错误恢复决策。
六、有序输出:Results 按接收顺序发送
并行执行还有一个隐含挑战:工具 A 先发起但后完成,工具 B 后发起但先完成,结果该按什么顺序给模型?
Claude Code 选择了**按接收顺序(FIFO)**输出,但有一个巧妙的”跳过”机制:
*getCompletedResults(): Generator<MessageUpdate, void> {
for (const tool ofthis.tools) { // 按加入顺序遍历
// 进度消息随时输出,不等排队
while (tool.pendingProgress.length > 0) {
yield { message: tool.pendingProgress.shift()! }
}
if (tool.status === 'yielded') continue
if (tool.status === 'completed' && tool.results) {
tool.status = 'yielded'
for (const message of tool.results) {
yield { message }
}
} elseif (tool.status === 'executing' && !tool.isConcurrencySafe) {
break// 非安全工具未完成时,阻塞后续输出(保持因果序)
}
// 安全工具未完成时,跳过继续检查后面的(不阻塞)
}
}
规则是:
-
• 并发安全工具:谁先完成谁先输出,不阻塞后面的 -
• 非并发安全工具:它没完成时,后面所有结果都等着(维护因果顺序) -
• 进度消息:随时输出,不参与排序
七、Fallback 与中断处理
流式执行还要处理两个边缘场景:
模型降级(Fallback)
如果 LLM 请求触发了降级(比如某个模型不可用,切换到备用模型),已经在跑的工具怎么办?
// query.ts — 降级时丢弃所有进行中的工具
if (streamingToolExecutor) {
streamingToolExecutor.discard() // 标记为废弃
streamingToolExecutor = newStreamingToolExecutor(...) // 创建新的
}
discard() 设置一个标志位,所有排队中和执行中的工具都会收到 streaming_fallback 合成错误,不会把过期的 tool_result 送给新模型。
用户中断(ESC 键)
privategetAbortReason(tool) {
if (this.discarded) return'streaming_fallback'
if (this.hasErrored) return'sibling_error'
if (this.toolUseContext.abortController.signal.aborted) {
if (signal.reason === 'interrupt') {
// 用户输入了新消息,检查工具的中断策略
returnthis.getToolInterruptBehavior(tool) === 'cancel'
? 'user_interrupted'
: null// 'block' 类工具不取消(如正在写的文件要写完)
}
return'user_interrupted'
}
returnnull
}
每个工具还可以声明自己的中断策略(interruptBehavior):cancel(立即取消)或 block(写完再停)。正在写文件的工具不会因为用户按了 ESC 就写一半。
八、总结:这个设计为什么值得学
用一张图概括整个架构:
┌─────────────────────────────────┐
│ LLM 流式输出 │
│ tool_use_1 tool_use_2 ... │
└──────┬────────────┬─────────────┘
│ │
┌────────▼────────────▼──────────┐
│ StreamingToolExecutor │
│ │
│ addTool() → processQueue() │
│ │ │
│ ┌────▼─────┐ │
│ │canExecute│ │
│ │Tool()? │ │
│ └──┬───┬──┘ │
│ │ │ │
│ ┌───▼┐ ┌▼───┐ │
│ │并行│ │串行│ │
│ │批次│ │批次│ │
│ └───┘ └────┘ │
│ │ │
│ getCompletedResults() │
│ (按序输出 + 进度实时推送) │
└─────────────────────────────────┘
五个值得搬进你的 Agent 的设计原则
1. 让工具自描述安全属性,而非在调度器里硬编码
每个工具通过 isConcurrencySafe(input) 自己声明。注意参数是 input——同一个工具,不同输入可能有不同的并发安全性(BashTool 根据命令是否只读来判断)。
2. 默认关闭(Fail-Closed)
buildTool 的默认值 isConcurrencySafe: () => false。开发者不声明 = 不允许并发。安全第一。
3. 不等模型说完就开始干活
LLM 的流式输出是一个 token 一个 token 到达的。当一个完整的 tool_use JSON 块解析完毕时,不需要等后面的 token,立即执行。
4. 分区批处理保持因果序
连续的安全工具合并为一个并行批次,遇到不安全工具就切分。这既最大化了并行度,又保证了有依赖关系的操作不会乱序。
5. 错误传播要分类
不是所有错误都该取消兄弟。Bash 命令有隐式依赖链,一个失败就全停;但文件读取是独立的,一个 404 不影响其他。针对工具语义定制错误传播策略。
后记
这个设计看起来不复杂,但要做到正确非常难。中断处理、降级回滚、有序输出、错误传播的每一个边缘 case 都可能导致状态不一致。Claude Code 的 StreamingToolExecutor 只有 530 行代码,但处理了全部这些 case,是一个非常精良的工程实现。
如果你正在构建自己的 Agent 框架,这个模式值得反复研读。核心思想只有一句话:
把等待变成执行,把串行变成并行,但在安全边界处绝不妥协。
本文基于 Claude Code 公开源码快照的安全研究分析。源码通过 npm 包 source map 暴露事件公开获取,本分析仅用于技术学习目的。
夜雨聆风