ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Claude Code 源码泄露了!从 npm Source Map 还原 TypeScript 源码的完整链路

Claude Code 源码泄露了!从 npm Source Map 还原 TypeScript 源码的完整链路 1. 一次打包疏忽把 TypeScript 源码送到了每个人手里Claude Code 源码泄露这件事本质上不是什么高深的安全攻防而是一次 npm 发包时的低级失误——发布包里忘了剔除 Source Map 文件。Source Map 是什么你可以把它理解成一张“翻译对照表”打包工具把 TypeScript 编译、压缩、混淆成浏览器或 Node 能跑的 JavaScript 之后这张表记录了每一段压缩代码对应原始文件的哪一行哪一列。它本来是给开发者调试用的一旦跟着 npm 包一起发出去任何人拿到它都能用现成工具把压缩后的代码“反向翻译”回接近原始的 TypeScript 结构。这次泄露的包里包含近 2000 个核心源文件模型权重和推理逻辑不在其中但 CLI 工具的工程架构、Agent 调度逻辑、工具调用链的设计几乎完整暴露。对普通用户来说这可能只是条新闻但对前端和 Node 开发者来说这是一次难得的、可以亲手复现的还原链路。下面我就按“拉包 → 定位 .map → 还原目录 → 验证完整性”的顺序把整条链路拆开讲清楚命令都可以直接复制运行。2. 复现前的前置准备Node 环境与 TaoToken 接入要复现还原过程你本地需要一套能跑 Node 脚本的环境。Node 18 以上即可npm 自带。如果你后续想对照还原出来的源码去理解 Agent 调度逻辑或者想自己跑一些模型调用来验证工具链行为可以顺手把 TaoToken 的接入配好——它提供统一的 API 入口兼容常见的模型调用格式配置一次就能在脚本里直接请求。TaoToken 的 API 地址是 https://taotoken.net/api 控制台在 https://taotoken.net/console API Keys 管理页在 https://taotoken.net/api-keys 。如果你打算长期做编码类实验或 Agent 相关开发可以了解下 Coding Planhttps://taotoken.net/coding-plan 。想先验证模型对话是否通用模型对话页https://taotoken.net/models 。接入文档在 https://taotoken.net/doc Claude Code 相关说明在 https://taotoken.net/claude-code 。这部分不是还原源码的必需步骤但如果你还原之后想跑通工具调用链、验证某些请求行为有一个稳定的 API 入口会省很多事。配置方式很简单在项目根目录建一个.env文件# .env TAOTOKEN_API_KEY你的key TAOTOKEN_BASE_URLhttps://taotoken.net/api然后在 Node 脚本里读取// config.js import dotenv/config; export const apiKey process.env.TAOTOKEN_API_KEY; export const baseURL process.env.TAOTOKEN_BASE_URL; if (!apiKey) { throw new Error(缺少 TAOTOKEN_API_KEY请检查 .env 文件); }这样后续任何需要发请求验证的脚本都能直接复用这份配置。3. 从 npm 拉包到定位 .map 文件第一步是把目标版本的包拉到本地。不要直接npm install到项目里那样会污染依赖树用npm pack下载 tarball 更干净mkdir claude-code-restore cd claude-code-restore npm pack anthropic-ai/claude-code --pack-destination ./pkg执行完你会看到pkg/目录下多了一个.tgz文件。解压它cd pkg tar -xzf anthropic-ai-claude-code-*.tgz cd package ls -la这时候重点来了找.map文件。Source Map 通常和编译产物放在一起命名规律是xxx.js.map。用 find 快速定位find . -name *.map -type f | head -50如果输出里出现大量.map文件说明这个包确实把 Source Map 一起发布了。你可以进一步统计数量find . -name *.map -type f | wc -l我实测下来这类失误包里的.map文件往往和.js文件一一对应。随便打开一个看看结构cat ./dist/cli.js.map | head -c 500你会看到类似这样的 JSON 结构{ version: 3, file: cli.js, sources: [../src/cli.ts, ../src/agent/dispatcher.ts], sourcesContent: [import ..., export ...], names: [], mappings: AAAA,IAAM... }关键字段是sources和sourcesContent。sources记录了原始文件路径sourcesContent如果存在就直接内嵌了原始源码文本——这意味着你甚至不需要做复杂的映射计算直接把sourcesContent抽出来就是源码。4. 用 source-map 工具还原完整目录结构如果sourcesContent存在还原非常简单。写一个 Node 脚本批量处理// restore.js import fs from fs; import path from path; import { glob } from glob; const MAP_DIR ./package; const OUT_DIR ./restored; async function restoreFromMap(mapFile) { const raw fs.readFileSync(mapFile, utf-8); const map JSON.parse(raw); if (!map.sourcesContent) { console.warn(跳过无 sourcesContent: ${mapFile}); return; } map.sources.forEach((src, i) { const content map.sourcesContent[i]; if (content null) return; // 去掉 ../ 前缀避免路径逃逸 const safeRel src.replace(/^(\.\.\/)/, ); const outPath path.join(OUT_DIR, safeRel); fs.mkdirSync(path.dirname(outPath), { recursive: true }); fs.writeFileSync(outPath, content, utf-8); }); console.log(已还原: ${mapFile} - ${map.sources.length} 个文件); } const maps await glob(${MAP_DIR}/**/*.map); console.log(共发现 ${maps.length} 个 map 文件); for (const m of maps) { await restoreFromMap(m); }运行npm init -y npm install glob node restore.js跑完之后restored/目录下就会出现还原出来的 TypeScript 文件树。你可以用tree或find看结构find restored -name *.ts | head -30 find restored -name *.ts | wc -l如果sourcesContent不存在那就需要用source-map库做真正的映射还原流程会复杂一些需要读取压缩后的 JS 和 mappings 字段逐段反解。但这次泄露的包里sourcesContent基本都在所以上面这个脚本就够用了。5. 验证还原结果是否完整还原完不能只看文件数量得验证内容是否完整、是否可读。几个检查动作第一检查文件是否为空或截断find restored -name *.ts -size -10c如果输出为空说明没有过小的异常文件。第二抽查关键文件的开头看是否是合法 TypeScripthead -20 restored/src/cli.ts正常应该看到 import 语句、类型定义、函数声明而不是乱码或压缩后的单行代码。第三统计还原文件数和 map 里声明的 sources 总数是否一致// verify.js import fs from fs; import { glob } from glob; const maps await glob(./package/**/*.map); let declared 0; let restored 0; for (const m of maps) { const map JSON.parse(fs.readFileSync(m, utf-8)); declared (map.sources || []).length; } const files await glob(./restored/**/*.ts); restored files.length; console.log(map 声明源文件数: ${declared}); console.log(实际还原文件数: ${restored}); console.log(declared restored ? 数量一致 : 存在差异需排查);第四用 TypeScript 编译器做语法检查看还原出来的代码是否能通过解析npx tsc --noEmit --allowJs false --skipLibCheck restored/src/cli.ts如果报大量语法错误说明还原过程中有内容损坏如果只是类型缺失报错那属于正常因为依赖没装。6. 常见报错与排查报错一Cannot find module glob说明依赖没装。执行npm install glob即可。如果你用的是 Node 20 以下glob 的 ESM 导入可能需要加--experimental-vm-modules或者改用glob的 CommonJS 版本glob8。报错二SyntaxError: Unexpected token export你的package.json里没有声明type: module。在npm init -y之后手动加上{ type: module }报错三还原出来的文件路径带../导致写到项目外面脚本里已经做了replace(/^(\.\.\/)/, )处理但如果你自己改脚本记得保留这一步。否则sources里的../../src/xxx.ts会写到上层目录污染你的工作区。报错四sourcesContent为 null部分 map 文件可能只存了 mappings 没存内容。这时候要么跳过要么用source-map库的SourceMapConsumer逐行反解import { SourceMapConsumer } from source-map; const consumer await new SourceMapConsumer(map); consumer.eachMapping(m { console.log(m.source, m.originalLine, m.originalColumn); });报错五还原后文件数远少于预期检查是不是只处理了顶层 map。用glob(./package/**/*.map)确保递归匹配所有子目录。另外有些包会把 map 放在dist/之外的目录find 的时候别限定路径。7. 还原之后能拿这份源码做什么还原只是第一步。拿到可读的 TypeScript 源码之后你可以对照着看它的 Agent 调度是怎么组织的、工具调用链是怎么串起来的、上下文是怎么裁剪的。这些工程细节在文档里通常不会写但在源码里一目了然。如果你想在本地跑一些验证脚本比如模拟一次工具调用请求可以用前面配好的 TaoToken 接入// test-call.js import dotenv/config; const res await fetch(${process.env.TAOTOKEN_BASE_URL}/v1/messages, { method: POST, headers: { Content-Type: application/json, x-api-key: process.env.TAOTOKEN_API_KEY, anthropic-version: 2023-06-01 }, body: JSON.stringify({ model: claude-sonnet-4-20250514, max_tokens: 256, messages: [{ role: user, content: 用一句话说明 Source Map 的作用 }] }) }); const data await res.json(); console.log(data.content?.[0]?.text ?? data);跑通之后你就能把还原出来的调度逻辑和自己的请求脚本对照起来看。想先验证模型对话是否正常可以直接用模型对话页https://taotoken.net/models 。需要管理 key 就去 API Keys 页https://taotoken.net/api-keys 。接入细节看文档https://taotoken.net/doc 。长期做编码和 Agent 实验的话Coding Plan 会更合适https://taotoken.net/coding-plan 。整条链路的核心其实就三件事npm pack 拉包、find 定位 map、脚本抽 sourcesContent。真正花时间的是验证环节——确认还原出来的文件不是空壳、不是截断、路径没写错。把这几个检查动作跑一遍你手里就有一份可读的 TypeScript 源码树了。
返回列表