news 2026/9/29 23:23:01

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 Plan:https://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_URL=https://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

如果输出为空,说明没有过小的异常文件。

第二,抽查关键文件的开头,看是否是合法 TypeScript:

head -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 版本glob@8。

报错二: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 源码树了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 23:20:03

Claude Code 提示词中英对照速查:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 23:19:55

把 AI 装进“记忆宫殿”:MemPalace 功能拆解与上手实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 23:19:36

Caffeine 缓存 详解

Caffeine 是 Java高性能堆内本地缓存库,Guava Cache 的继任者,Spring5 推荐本地缓存实现,核心亮点是 W-TinyLFU淘汰算法,高并发、高命中率,适合单机热点数据缓存。 本质:JVM内存缓存,单机有效&a…

作者头像 李华
网站建设 2026/9/29 23:17:29

Mobius大模型FP16稳定运行指南:rescale_every权重缩放机制揭秘

Mobius大模型FP16稳定运行指南:rescale_every权重缩放机制揭秘 【免费下载链接】Mobius大模型 Mobius大模型是开放原子基金会开源项目,采用OpenAtom-Model-License-V1.0协议。具备强大的自然语言理解与生成能力,支持多场景应用开发&#xff0…

作者头像 李华