ClaudeCode(2.1.283)逆向和完整遥测分析
原创 为了安全鸭 2026-10-05 22:00 北京

前言
最近在开中转站,众所周知sub2api的特征是非常多的,所以我完整的逆向了cladue code的全部协议一比一对特征解决特征,但是越来越发现这玩意非常的离谱,有些已经超出代码编辑工具的边界了,故在此公开分析报告,我用的是我电脑上的Claude code 的2.1.283的版本。
Claude code的遥测
Claudecode存在一个遥测系统,而这个Claude code的遥测系统不会随着你用中转站而关闭。也就是说你用了他们的产品就会发送遥测数据给Claude code,除非你使用
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1这个环境变量把他关闭。
function gmt() {
if (process.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC)
return "essential-traffic";
if (process.env.DISABLE_TELEMETRY)
return "no-telemetry";
if (Ne(process.env.DO_NOT_TRACK))
return "no-telemetry";
return "default";
}
function It() {
return gmt() === "essential-traffic";
}
function $D() {
return gmt() !== "default";
}一旦遥测打开,后续的信息都会发送给:
https://api.anthropic.com/api/event_logging/v2/batch让我们看看他有什么行为
Claude code唯一标识符
当你装Claude code的时候,cc会在你电脑上生成一个随机的标识符
// 生成随机编号。
let s = Ed(32).toString("hex");
// 写入本地配置,字段名叫 userID。
Ee(g => ({
...g,
userID: s
}), e);跟外挂机器码修改一样,如果你不处理它,你就会被永久标记
模型信息
如果你用第三方API,比如zhipu的或者deepseek,Claude code依然会上传上去模型使用信息,会话ID,用户类型
模型使用信息的逻辑是:
// 取得当前模型名称。
let n = e.model ? String(e.model) : it();
// 经过客户端转换。
D = Jdn(n);
// 放入元数据。
model: D,他会把模型名字去
https://downloads.claude.ai/model-catalog/v1/catalog.json
```javascript
bne = [
"claude-3-5-haiku",
"claude-3-5-sonnet",
"claude-3-7-sonnet",
"claude-fable-5",
"claude-fable-5-1",
"claude-haiku-4-5",
"claude-mythos-5",
"claude-mythos-5-1",
"claude-opus-4-0",
"claude-opus-4-1",
"claude-opus-4-5",
"claude-opus-4-6",
"claude-opus-4-7",
"claude-opus-4-8",
"claude-opus-5",
"claude-opus-5-5",
"claude-sonnet-4-0",
"claude-sonnet-4-5",
"claude-sonnet-4-6",
"claude-sonnet-5"
];
j1 = [
"sonnet",
"opus",
"haiku",
"fable",
"best",
"sonnet[1m]",
"opus[1m]",
"fable[1m]",
"opusplan"
];
var Fpt = "claude-mythos-preview";拉一个列表下来,如果模型不在这里面,会标记为”confidential”如果在这里,会原样上传模型名字
token使用情况
即便是不用Claude订阅,走第三方API,也一样会上传你的token使用情况
包括不限于
压缩时候的情况

你的当前中转站URL(ANTHROPIC_BASE_URL)
function nMe() {
return {
...process.env.ANTHROPIC_BASE_URL && {
baseUrl: XMo(process.env.ANTHROPIC_BASE_URL)
},
...process.env.ANTHROPIC_MODEL && {
envModel: xt(process.env.ANTHROPIC_MODEL)
},
...process.env.ANTHROPIC_SMALL_FAST_MODEL && {
envSmallFastModel:
xt(process.env.ANTHROPIC_SMALL_FAST_MODEL)
}
};
}同一个客户端/会话用了多少 token、请求速度、是否重试、用了哪些运行模式、输入是否含图片/文档、system 和工具定义多大,以及一些可关联的哈希和请求编号。
这块非常多,看图吧:

其中有意思的是 default_model
我们上面说了model会脱敏,而default_model的”脱敏”是一种伪脱敏,具体来说,他会过一层正则
var Bw = /^[A-Za-z0-9._:[\]-]{1,100}$/;
var Kw =
/^[A-Za-z0-9._:[\]-]{1,91}@\d{8}(\[\d{1,3}[mM]\])?$/;
function xt(e) {
if (e == null)
return;
return Bw.test(e) || Kw.test(e)
? bn(e)
: y("nonconforming");
}如果是这种格式
claude-sonnet-4-5
gpt-4.1
deepseek-chat
qwen3-32b
my_model:v1
sonnet[1m]则会变成”confidential”
而如果是这种
provider/model 含 /
my model 含空格
模型一 含中文
model?version=1 含 ? 和 =则会被标记
我能想到的场景是,我自己测评的模型,比如自己训练的CTF的模型,会被hit,如果用vllm,一般默认的后缀就是目录/模型名字比如这样:

从而变成”nonconforming”
skills和插件
当你用到skill的时候(请求中用了skill),无论是不是claude订阅还是第三方,都会上传skill名字
function _W({
rawName: e,
canonicalName: n,
isMcp: r,
isBuiltIn: s,
isBundled: g,
isOfficial: h
}) {
let S = r
? "mcp"
: s || g || h
? e
: "custom";
return {
sanitizedName: bn(S),
skillNameHash:
S === "custom" ? lhr(n) : {}
};
}你的插件也是,也会上传对应的名字
function bW(e, n, r = null) {
let s = Kge(e, n) ?? n;
return {
_PROTO_plugin_name: e,
...s && {
_PROTO_marketplace_name: s
},
...zcn(e, n, r)
};
}MCP与工具使用
claude code会上传你的工具使用情况,包含工具名字

而如果是MCP服务器,自定义的,则会脱敏走hash上传
var fP = "claude-plugin-telemetry-v1";
function aye(e) {
return cP("sha256")
.update(e + fP)
.digest("hex")
.slice(0, 16);
}有意思的是还会上传你是拒绝还是同意,你为此等了多久(wtf)

i("tengu_tool_use_rejected_in_prompt", {
...kP(e, n, s, h),
...g,
...S,
...r.type === "hook"
? { isHook: !0 }
: {
hasFeedback:
r.type === "user_reject"
? r.hasFeedback
: !1
}
});运行环境
这些是挺标准的:

然而在以下条件下,Claudecode会额外上传tag
github的action
isGithubAction:
Ne(process.env.GITHUB_ACTIONS),
isClaudeCodeAction:
Ne(process.env.CLAUDE_CODE_ACTION)如果是action环境,上传
事件类型、runner 环境/系统、action ref
WSL
通过字符串比对是否是微软的WSL:
getWslVersion() {
if (this.wslVersion !== null)
return this.wslVersion;
if (this.sources.platform !== "linux") {
this.wslVersion = void 0;
return;
}
let e = this.kernelString();
if (e === void 0) {
this.wslVersion = void 0;
return;
}
let r = e.match(/wsl(\d+)/);
if (r && r[1])
this.wslVersion = r[1];
else if (e.includes("microsoft"))
this.wslVersion = "1";
else
this.wslVersion = void 0;
return this.wslVersion;
}如果是微软的WSL,则会上传WSL 版本、Linux 发行版及版本、内核信息
docker
如果你在docker下用它,他会通过
if (process.env.KUBERNETES_SERVICE_HOST)
return "kubernetes";
if (o.isDockerenvPresent())
return "docker";
if (p.platform === "darwin")
return "unknown-darwin";
if (p.platform === "linux")
return "unknown-linux";
if (p.platform === "win32")
return "unknown-win32";
return "unknown";识别你是不是docker
进程信息
会上传当前进程的信息

git信息
Claude code会上传你的git url的前20字节并且做hash
async function ZWr() {
let e = await Vne();
if (!e)
return null;
let n = w2(e);
if (!n)
return null;
return So("sha256")
.update(n)
.digest("hex")
.substring(0, 16);
}并且知道你的当前 HEAD 的提交 SHA
猜测是检查,哪些账户在开发相同的项目(类似于组织指纹)
如果你把Claude code放到github action,他会直接上传github的信息:
if (n.githubActionsMetadata) {
let se = n.githubActionsMetadata;
ee.github_actions_metadata = {
actor_id: se.actorId,
repository_id: se.repositoryId,
repository_owner_id: se.repositoryOwnerId
};
}操作者 ID;
仓库 ID;
仓库所属用户/组织 ID。
GrowthBook云控
在启动的时候,Claude code会发送

到
普通评估:
POST https://api.anthropic.com/api/eval/sdk-zAZezfDKGoZuXXKe
带认证评估分支:
POST https://api.anthropic.com/api/eval-authed/sdk-zAZezfDKGoZuXXKe随后看服务端返回了什么云控功能开关:
{
"features": {
"某个实验功能": {
"value": true,
"source": "experiment",
"experiment": {
"key": "实验编号"
},
"experimentResult": {
"variationId": 1
}
}
}
}这个是典型的云控行为,有意思的是这个云控数量达到了惊人的648个,而且很多意义不明,这里挑出来一些AI跑过的
tengu_heron_brook 远程服务端提示词注入
这个神奇的flag接受远程服务端的提示词并且注入到系统上下文中
客户端会读一个client_data
GET <base>/api/claude_cli/bootstrap
→ 响应中的 client_data
→ 本地 clientDataCacheSlots
→ cd()然后判断这个key
function hXn() {
let e = nSt();
if (!e)
return null;
return i("tengu_heron_brook_applied", {
len: e.value.length,
fromClientData: e.fromClientData
}), e.value;
}然后塞到system提示词里面。
这意味着如果官方给你换了一个能执行任意代码的模型 结合这个 就能实现任意代码执行。
当然我们不能那么坏的去看A÷是吧
tengu_official_plugin_prompt_overrides 覆盖插件提示词
官方可以下云控,去控制自己的MCP和工具的提示词,这里要说明的是,不是任意的,是官方自己的
{
server_instructions: string,
server_instructions_by_server: { [serverName]: string },
tools: { [toolName]: string },
search_hints: { [toolName]: string },
param_descriptions: {
[toolName]: { [parameterName]: string }
},
prompts: { [promptName]: string },
skills: { [skillName]: string }
}字段 | 实际用途 |
|---|---|
server_instructions | 覆盖 MCP 连接 server instructions |
server_instructions_by_server | 按 server 名覆盖 instructions |
tools | 覆盖已有 MCP 工具 description |
search_hints | 覆盖已有工具搜索提示 |
param_descriptions | 改写已有 input schema properties 的 description |
prompts | 覆盖 MCP prompt 的 description |
skills | 覆盖发现的 MCP skill 的 description |
tengu_max_version_config 更新时候的最低版本
云控设置一个最低版本,更新的版本不能超过这里配置的版本
async function Qwe() {
let e = await Pe();
let n = e.external || void 0;
let o = n ? R.parse(n)?.version ?? void 0 : void 0;
// 原函数还有非法版本告警;此处省略。
return {
maxVersion: o,
forceDowngradeEnabled:
e.external_force_downgrade === !0
};
}
async function Pe() {
try { return await k1("tengu_max_version_config", {}); }
catch (e) { return d(e), {}; }
}sub2api等各种中转站检测
众所周知 x-anthropic-billing-header是带了cc_version这个字段的。
cc_version这个字段官方 Claude Code 取第一条 user 文本的第:
4、7、20三个位置,然后计算:
SHA256(
"59cf53e54c78"
+ text[4]
+ text[7]
+ text[20]
+ "2.1.283"
) 的前 3 位十六进制官方是 JavaScript,所以字符串下标按 UTF-16 字符单元。
而sub2api项目的当前 Go 代码:
chars = append(chars, firstText[i])按的是 UTF-8 字节
所以会导致sub2api的项目,真实 Claude Code 先生成:
cc_version=2.1.283.<官方后缀>;请求到 SUB2API 后,当前项目的:
https://github.com/Wei-Shaw/sub2api/blob/a60a29549f488a854966aaec9541abbe006cac22/backend/internal/service/gateway_billing_block.go#L32
syncBillingHeaderVersion()会重新计算后缀,但项目按 UTF-8 字节取第 4、7、20 位,而官方按 JavaScript UTF-16 下标取值。
于是可能变成:
客户端:
cc_version=2.1.283.d80;
SUB2API 出站:
cc_version=2.1.283.fdc;导致sub2api被检测
结论
最后附上gpt-6.1-sol的结论
本文转载自微信公众号「冲鸭安全」,查看原文。版权归原作者所有,如有侵权请联系删除。