5826 字
29 分钟
Claude 客户端(CLI 2.1.306 与 Desktop 3.1623.0)设备识别与网络遥测机制逆向报告

针对社区中关于“多账号连续封禁是否源于客户端底层硬件锁(如主板序列号、MAC 地址或 TPM 芯片指纹)”的讨论,本报告对 Windows 平台下的 Claude Code CLI(2.1.306) 与 Claude Desktop GUI(3.1623.0) 进行了静态代码提取、底层原生模块扫描及本地落盘数据核对。

分析范围涵盖 CLI 端的 281.9 MB Bun 单文件可执行程序(提取出 3983 个明文 JS 模块,共 25.9 MB),以及 Desktop 端的 app.asar 归档(解包 509 个文件,含 477 个 JS 脚本,共 40.3 MB)与 3.8 MB 的 Rust 原生扩展模块 claude-native-binding.node。本报告仅陈述客户端侧可观测的代码逻辑与本地持久化证据,不对服务端风控算法与封号阈值做主观推断。


一、 核心结论摘要#

在讨论“硬件识别”时,需先区分两个概念:唯一硬件标识(物理序列号/机器码)与设备环境指纹(软硬件配置组合)。前者的特征是出厂固定或跨软件共享,可直接作为数据库主键;后者则是多个型号级与配置级字段的组合,用于辅助提高风控聚类的置信度。

基于四重静态与落盘证据(本机落盘文件、原生二进制双编码扫描、跨模块调用链追踪、注册表字面量全枚举),核心结论如下:

  1. 未采集任何物理硬件序列号或系统机器码:两个客户端均未采集网卡 MAC 地址、磁盘物理序列号、主板/BIOS/SMBIOS 序列号或 CPU 序列号。对于操作系统级机器码(如 Windows MachineGuid、macOS IOPlatformUUID、Linux /etc/machine-id),Desktop 客户端已将其完全移除;CLI 客户端虽包含读取代码,但因唯一调用方 hostDetector 未在任何遥测路径中注册,实际属于从不执行的死代码。

  2. 跨账号关联的首要通道是本地随机安装 ID:CLI 端通过 ~/.claude.json 持久化 64 位十六进制随机数 userID 与 machineID;Desktop 端通过 %USERDATA%\ant-did 持久化随机 UUIDv4。这些标识纯属随机生成,与物理硬件无关,但其生命周期与账号登录态解耦——切换账号、退出重登均不会重置这些 ID。若未手动清理配置文件而在同一客户端连续切换多个账号,服务端在所有请求与遥测中看到的都是同一个设备 ID。

  3. Desktop 端通过 Sentry 默认收集完整的设备环境指纹:虽然没有物理序列号,但 Desktop 端的 Sentry 错误上报上下文(持久化于本地 scope_v3.json)默认组装了 CPU 型号、逻辑核数、精确物理内存、最多 3 块 GPU 的型号/PCI ID/驱动版本、屏幕分辨率与缩放比例、精确到毫秒的开机时间(boot_time)以及时区语言,并将 user.id 绑定为 ant-did。

  4. TPM 2.0 仅用于凭据防盗,而非硬件封禁:Desktop 端调用 Windows PlatformCryptoProvider 在 TPM 芯片内生成密钥对,但并未读取出厂背书密钥(EK)或执行硬件平台证明(TPM2_Quote)。其生成的椭圆曲线密钥对按 accountUuid 独立隔离,不同账号拥有完全独立的随机公钥,服务端无法借此判定两个公钥来自同一颗物理 TPM 芯片。

  5. CLI 端存在灰度中的 Git 仓库地址上报逻辑:CLI 2.1.306 内置了名为 x-claude-code-wiggly-dove 的自定义请求头逻辑,受 GrowthBook 特性开关 tengu_wiggly_dove 控制(当前默认 false)。一旦远程开启,客户端会解析当前工作区 .git/config 中的 remote origin URL 并随每次 API 请求发送。


二、 Claude Code (CLI 2.1.306) 逆向分析#

Claude Code 2.x 采用 Bun --compile 打包为单文件原生程序,其业务 JS 源码以明文形式嵌入在二进制中。通过按连续可打印字节切分并结合模块的 export{... as ...} 导出表进行跨 chunk 符号校验,可完整还原其内部调用链。

1. 操作系统机器码读取:存在实现但无调用出口#

在二进制偏移 246,053,789 处存在平台分派函数 getMachineId(),可根据 process.platform 动态加载各操作系统的机器码读取实现:

  • Windows(偏移 266,138,923):调用 %windir%\System32\REG.exe QUERY HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography /v MachineGuid 提取注册表值。

  • macOS(偏移 266,135,306):执行 ioreg -rd1 -c "IOPlatformExpertDevice" 提取 IOPlatformUUID。

  • Linux 与 FreeBSD:Linux 依次尝试读取 /etc/machine-id 与 /var/lib/dbus/machine-id;FreeBSD 读取 /etc/hostid 或回退至 kenv -q smbios.system.uuid。

然而,对全部 3983 个模块进行符号引用统计后发现,这组代码在当前版本中并未生效:

// 偏移约 246,054,895:OpenTelemetry SDK 内部的 hostDetector
class _A {
detect(e) {
return { attributes: {
[dt.ATTR_HOST_NAME]: nn.hostname(),
[dt.ATTR_HOST_ARCH]: mA.normalizeArch(nn.arch()),
[dt.ATTR_HOST_ID]: pA.getMachineId() // 全量代码中唯一的 getMachineId 调用点
}};
}
}

全量扫描结果显示,getMachineId 出现的 8 处全部局限在 OpenTelemetry(OTel)resource 模块内部,hostDetector.detect(...) 的实际调用次数为 0。进一步排查客户端内全部三条 OTel Resource 构造路径,均可确认 hostDetector 被排除在外:

  • 路径 A(1P 官方遥测 /api/event_logging/v2/batch):在 HGe()(偏移 247,628,495)中直接通过 resourceFromAttributes 显式构造,仅包含 service.name、service.version 以及 WSL 环境下的 wsl.version,不经过任何 detector。

  • 路径 B(3P 企业 OTLP 导出):在构造器 it()(偏移 280,337,857)中,代码显式调用了 osDetector.detect()(获取系统类型与版本)、host.arch(仅 CPU 架构)以及 envDetector.detect()(读取 OTEL_RESOURCE_ATTRIBUTES),唯独跳过了 hostDetector,因此不会产生 host.id 与 MachineGuid。

  • 路径 C(defaultResource() 兜底):仅返回 service.name 与遥测 SDK 语言/版本元数据,不执行任何 detector。

此外,即便未来版本启用了 MachineGuid 读取,从技术属性上看,MachineGuid 也只是 Windows 系统安装实例的编号(重装系统即变,克隆磁盘则会重复,换主板/CPU 不变),并非物理硬件序列号。

2. 常见可疑字符串的误判排除#

在静态字符串检索中,二进制内会出现若干与硬件或网络相关的系统命令与 API,经上下文核对均与硬件指纹采集无关:

  • systeminfo、getmac、wmic:仅出现在 Bash 工具的命令安全分类表中(例如 getmac: { safeFlags: ["/FO", "/NH", "/V"] }),用于判断 AI 尝试执行的终端命令是否属于只读安全命令、是否需要向用户弹出权限确认提示。

  • networkInterfaces()(共 9 处):分别用于 Google Compute Engine(GCE)云环境探测(比对 Google MAC 地址段)、本地沙箱与代理网络配置,以及 Node.js 内部实现,无 MAC 地址外发逻辑。

  • serialNumber(共 27 处):全部属于 PKI/ASN.1 证书解析字段或 @parcel/watcher 文件系统监控中的卷序列号(volumeSerialNumber,用于检测本地文件变更)。

3. CLI 持久化标识与遥测载荷结构#

CLI 客户端真正用于标识设备的字段存储在用户目录的 ~/.claude.json 中,由函数 Obt(getOrCreateMachineID)和 p0(getOrCreateUserID)在偏移 246,991,000 附近生成:

function Obt(e) { // getOrCreateMachineID
let n = ce(), r = ue();
if (typeof r.machineID === "string" && Ds.test(r.machineID)) return r.machineID;
if (n.generatedMachineID) return n.generatedMachineID;
let s = Ju(32).toString("hex"); // 32 字节纯随机数(64 位 hex)
return n.setGeneratedMachineID(s), Re((h) => ({ ...h, machineID: s }), e), s;
}

这两个 ID 均为 randomBytes(32).toString("hex") 生成的纯随机字符串,另有随机 UUID 形态的 remoteControlMachineId 专用于远程控制/云会话。它们在网络请求中的映射关系如下:

  • 常规 API 请求头:仅携带 User-Agent: claude-cli/2.1.306 (external, cli) 与每次会话随机生成的 X-Claude-Code-Session-Id,不携带机器标识或用户名。

  • 1P 遥测事件(/api/event_logging/v2/batch):将本地 userID 映射为外发 JSON 中的 device_id(以及 user_metadata.deviceId)。其 env 块仅包含平台、架构、Node/Bun 版本、终端与 Shell 类型、CI/容器环境标记及 Git 工作树状态统计(如 head_sha、变更文件数),不含主机名、用户名或机器码。

  • 错误上报(Sentry / Datadog 格式):将 hostname 字段硬编码为 "claude-code",同时截取本地 machineID 的前 12 位填入 host_name_redacted 字段。

  • 真实主机名(os.hostname())的外发边界:全量 19 处引用中,仅在用户主动发起 Remote Control 云会话注册(作为 machineName)或同步设备 MCP 授权(device_mcp_consent 中的 machine_name)时才会上传真实主机名。

由于 userID 和 machineID 存储在全局配置层级,不随 oauthAccount 的登出或切换而重置,同一配置文件下的多个账号在遥测流中会共享完全相同的 device_id。

4. 灰度中的敏感数据采集:x-claude-code-wiggly-dove#

在模块 run-1625-248304930.js(偏移 250054 起)中,内置了一套按 RFC 8941 字典规范组装动态客户端元数据的自定义请求头逻辑,头部名称为 x-claude-code-wiggly-dove:

var qc = "x-claude-code-wiggly-dove";
var Wc = "983fad3bbb6cff8714b9f0c8bce0d76386cabd5e65e046625c159df3b953f99d"; // qc 的 SHA-256
function ci(e) {
let r = e.toLowerCase();
return r === qc || kb("sha256").update(r).digest("hex") === Wc; // 防用户自定义变量覆盖校验
}
var xb = "tengu_wiggly_dove"; // GrowthBook 远程灰度开关

该头部使用双字母混淆短码记录环境特征(如 ob 为版本号、sk 为客户端入口、tt 为权限模式、bf 为沙箱分类、vd 为主机环境),其中键名 jy 对应采集函数 Db()(偏移约 254000):

  • Db() 内部调用 svt()(位于 run-1506-245151486.js 偏移 76504),直接读取当前工作目录 .git/config 文件中 [remote "origin"] 段落下的 pushurl 或 url。

  • 剥离查询参数并截断至 126 字符后,拼入每次 API 请求的默认头部:x-claude-code-wiggly-dove: v=1, yw=:jm:, ob="2.1.306", sk="cli", jy="github.com/org/repo"。

该逻辑目前受 E("tengu_wiggly_dove", false) 门控,默认未开启;但若服务端通过 GrowthBook 下发策略开启该开关,同一本地代码仓库下的不同账号将通过 jy 字段暴露相同的 Git 远程仓库地址。


三、 Claude Desktop (GUI 3.1623.0) 逆向分析#

Windows 版 Claude Desktop 是基于 MSIX 托管的 Electron 应用(Electron 44.9.0 / Chromium 152 / Node 24.22.0),包含主进程/渲染进程 JS 包、Rust 编写的 claude-native-binding.node 以及 Go 编写的后台服务 cowork-svc.exe。

1. 物理序列号与系统机器码的“负命题证明”#

为确认 Desktop 客户端是否采集底层硬件标识,分析不仅对 477 个 JS 文件及原生二进制(.node 与 cowork-svc.exe)执行了 ASCII 与 UTF-16LE 双编码扫描,还对主进程代码中出现的所有 Windows 注册表路径字面量进行了全量枚举。

扫描确认:MachineGuid、IOPlatformUUID、IOPlatformSerialNumber、/etc/machine-id、Win32_ComputerSystemProduct、Win32_BaseBoard、Win32_DiskDrive、Win32_BIOS、GetVolumeInformation、GetAdaptersInfo 等字符串在全量 JS 与原生层中的命中数均为 0。

同时,正则枚举主进程中的注册表路径字面量,共提取出全部 15 条访问路径:

SOFTWARE\Microsoft\Windows NT\CurrentVersion (读取 BuildLabEx / UBR)
SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList (枚举本地用户目录,用于日志脱敏)
SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI
SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\SyncRootManager
SOFTWARE\Policies\ClaudeCode
SOFTWARE\Google\Chrome\NativeMessagingHosts
SOFTWARE\Google\Chrome\Extensions
SOFTWARE\Microsoft\Edge\NativeMessagingHosts
...(其余 7 条均为 Brave / Vivaldi / Opera / Arc / Island / Prisma 等浏览器的 NativeMessagingHosts)

枚举结果提供了坚实的负命题支撑:

  • MachineGuid 不在访问面内:存放 MachineGuid 的 SOFTWARE\Microsoft\Cryptography 根本不在主进程的注册表读取列表中。

  • ProfileList 与 LogonUI 的真实用途:代码(偏移 286100–288200)虽然枚举了本机所有安全标识符(SID)的 ProfileImagePath 与登录用户名,但其唯一用途是在生成本地诊断日志时执行路径脱敏(将 C:\Users\Alice 替换为 <user>),并未作为遥测字段上传。

  • 网卡 MAC 地址仅驻留内存:index.chunk-DWDY4R3F.js(偏移 257345)中每 3 秒执行一次 networkInterfaces() 并构造 ${e.family}|${e.address}|${e.mac} 签名,但该签名仅用于在内存中比对网络切换事件,以便在 Wi-Fi 与有线网络切换时打印日志并重探 SSH 控制器(probing SSH controllers),不写入磁盘亦不进入网络载荷。

2. 核心安装标识:ant-did 的生成与跨端注入#

Desktop 端的核心持久化标识存储在 %LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\ant-did 文件中。其生成逻辑位于 index.chunk-7dsQFims.js(偏移 2045997):直接调用 crypto.randomUUID() 生成纯随机的 UUIDv4,经 Base64 编码后以 0o600 权限落盘;若文件未删除,该值在应用生命周期内保持只读,不随账号切换或重登而改变。

ant-did 在 Desktop 客户端中的覆盖面极广,贯穿以下 8 个场景:

  1. 每次 Anthropic API 请求:通过请求头 anthropic-device-id 发送;

  2. GrowthBook 特性开关拉取:通过请求头 anthropic-device-id 发送(@2053543);

  3. GrowthBook 实验曝光事件:作为 device_id 字段发送(@2059897);

  4. 1P 业务遥测(event_logging):作为 metadata.install_id 发送(@3061696);

  5. 自动更新检查:作为 URL 查询参数 device_id=<ant-did> 发送(@2400654);

  6. Sentry 错误上报:直接写入 initialScope.user.id 与 session.did;

  7. 设备信任注册:作为 getLocalDeviceInfo 中的 deviceId;

  8. 客户端 UI 菜单:对应界面上的“Copy Installation ID”选项。

此外,当 Desktop GUI 调起内置的 CLI 会话时,存在一处受 GrowthBook 开关 "2220415149" 控制的跨端身份注入逻辑(位于 index.chunk--MCmm2mz.js 偏移 475226):

async seedStableDeviceIdIntoSession(e) {
if (!t.h1("2220415149")) return; // GrowthBook 特性开关门控
let n = je(); // 计算 sha256(ant-did)
if (n) try {
await na(this.getClaudeConfigDir(e), n); // 将哈希值覆写进目标 .claude.json 的 userID 字段
} catch (n) { ... }
}

一旦该特性开关开启,GUI 会将 sha256(ant-did) 直接写入子 CLI 会话配置文件 .claude.json 的 userID 字段中(不修改 machineID),从而将桌面端与 CLI 端的安装标识关联起来。

3. 三条设备环境数据外发链路与 Sentry 配置指纹#

Desktop 客户端向外发送设备环境信息的通道共有三条,其特征粒度呈递进关系:

  • 链路 C(每次 API 请求头,@2045614):携带 anthropic-client-device-class(按内存分为 le4 / 8 / 16 / gt16 四档)、anthropic-client-total-memory-gb(总内存 GB 数)、anthropic-client-os-platform、anthropic-client-os-version 以及 anthropic-device-id。

  • 链路 B(1P 遥测 /api/event_logging/v2/batch,@3061696):在 metadata 中额外增加 cpu_model(os.cpus()[0].model)、精确内存字节数,以及从注册表读取的 Windows 完整构建标签 BuildLabEx(os_build)与累积更新补丁号 UBR(os_build_revision)。

  • 链路 A(Sentry 错误监控,index.pre.js @963674):维度最全的设备环境指纹链路。

在主进程初始化 Sentry SDK 时,代码将 initialScope.user.id 设为 w5()(即 ant-did),且 GPU 上下文收集开关 dropGpuContext 仅在命令行显式传入 --disable-gpu 参数时才为真(正常启动一律收集)。直接读取测试机本地落盘的 %LOCALAPPDATA%\...\Claude\sentry\scope_v3.json 快照,可观测到已组装就绪的完整设备上下文:

上下文分类字段名实机落盘示例值跨账号稳定性与区分度说明
用户标识user.id / session.did7fcd29ad-3cd3-4eed-96ac-d4da39e250f1等于 ant-did,不删文件则跨账号恒定
处理器device.cpu_descriptionIntel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz型号级特征,随硬件更换改变
核心与频率processor_count / frequency36 / 2295逻辑核数与主频
物理内存device.memory_size68563431424 (约 64 GB)精确到字节的物理内存容量
显卡列表gpu_1 / gpu_2 / gpu_3双卡 RTX 2080 Ti (0x10de:0x1e07) + Basic Render含型号、PCI 厂商与设备 ID、驱动版本 32.0.16.1692
显示屏幕screen_resolution / density2880x1800 / 1.5分辨率与系统 DPI 缩放比例
开机时间device.boot_time2026-09-17T09:01:15.964Z重启即变,但同一次开机内精确到毫秒恒定
系统与区域os.version / timezone / locale10.0.26200 / Asia/Shanghai / zh-CN操作系统版本与区域设置

对上述字段的评估表明:虽然其中没有任何单项属于全球唯一的硬件序列号(例如 PCI ID 0x1e07 仅代表 RTX 2080 Ti 这一显卡型号),但当 “特定 CPU 型号 + 逻辑核数 + 精确内存字节 + 多显卡组合与特定驱动版本 + 屏幕分辨率与缩放比例” 组合在一起时,已在很大程度上构成了高区分度的配置指纹。特别是在单次开机周期内,多个账号若交替登录并触发上报,毫秒级一致的 boot_time 配合相同的硬件配置组合,足以形成强关联信号。

4. 出网注册表(Egress Registry)与遥测开关归属#

在 index.chunk-7dsQFims.js(偏移 833800–840500)中,客户端通过声明式出网注册表定义了 11 项外部网络服务及其对应的控制开关:

控制开关管辖的服务项对应端点与行为说明
无开关(常驻)vm-bundle、model-catalog、dictation连接 claude.ai(拉取工作区包、5–15 分钟轮询模型目录)及听写服务主机
disableAutoUpdatesupdater/api/desktop/.../update 与 /releases/ 自动更新检查
disableNonessentialServicesconnector-favicons、artifact-sandbox、mcp-app-sandbox、spellcheck-dictionaryGoogle 图标服务、claudeusercontent.com、claudemcpcontent.com、Linux 拼写词典 gvt1.com
disableNonessentialTelemetryanthropic-telemetry仅阻断 /api/event_logging/ 使用分析(含渲染层与 CLI 子进程遥测)
disableEssentialTelemetrysentry、datadog-rum阻断 *.sentry.io 错误上报与 /api/v2/rum 及 7 个 Datadog 域名

此处有三个关键事实需要注意:

  1. Sentry 归属于“必要遥测(Essential Telemetry)”:仅开启 disableNonessentialTelemetry 只能关闭 event_logging,无法关闭携带完整设备配置指纹的 Sentry 上报;必须启用 disableEssentialTelemetry 才会触发 y5a("disableEssentialTelemetry") 将 Sentry 禁用(且这两个开关在配置定义中仅对 3p 部署暴露)。

  2. Datadog RUM 仅为白名单声明:虽然注册表列出了 Datadog RUM 端点,但全量代码扫描 datadogRum、DD_RUM、@datadog/ 均为 0 命中;桌面端 Shell 并未打包 RUM SDK,该项仅为远端 Web 渲染层(claude.ai)预留的 CSP 与防火墙出网白名单。

  3. 崩溃内存转储(Minidump)经由 Sentry 发送:虽然 Electron 原生的 crashReporter.start 被设为 uploadToServer: false(提交地址为占位符 https://f.a.k/e),但 Sentry 集成会主动读取 %USERDATA%\Crashpad 目录下的 .dmp 进程内存快照作为附件上传,限制为每会话最多 10 个(maxMinidumpsPerSession)。


四、 TPM 2.0 与设备信任机制(Trusted Devices)分析#

CLI 与 Desktop 客户端中均包含一套“设备信任(Trusted Devices)”机制,涉及请求头 X-Trusted-Device-Token、403 untrusted_device 自动注册重试,以及 Desktop 原生模块 claude-native-binding.node 中的 TPM 2.0 硬件调用。

1. 原生层调用链路#

通过对 3.8 MB 的 claude-native-binding.node(由 Rust windows-0.58.0 编译)进行字节扫描与符号追踪,还原出其底层加密调用栈如下:

claude.exe
→ require("@ant/claude-native") (源码路径:src\hardware_key\win32.rs)
→ NCryptOpenStorageProvider("PlatformCryptoProvider")
→ NCryptCreatePersistedKey(ECC P-256)
→ NCryptExportKey(ECCPUBLICBLOB)
→ NCryptSignHash(...)
→ 导出公钥并注册至服务端 DeviceRegistry

当本机不支持 TPM 或 PlatformCryptoProvider 不可用时,原生模块会降级调用 Windows DPAPI(CryptProtectData / CryptUnprotectData / userDpapi)使用当前系统用户凭据加密保存软件密钥,并在遥测事件 cowork_remote_attestation 中将证明类型标记为 td_v2_software_shim(TPM 可用时标记为 td_v2_hardware)。

2. 为何 TPM 调用不构成“跨账号硬件锁”?#

在安全机制上,TPM 的应用分为密钥存储模式(KSP)与硬件平台证明模式(Platform Attestation)两类。前者仅把芯片当作不可导出私钥的密码学协处理器;后者则会读取芯片出厂烧录的唯一背书密钥(Endorsement Key, EK)以证明物理硬件身份。

三项代码证据表明,Claude 客户端仅使用了前一种模式:

  1. 无背书密钥与硬件证明调用:在原生模块二进制中,针对 NCRYPT_PCP_EKPUB_PROPERTY、EKPUB、Tbsi_Context_Create、Endorsement、Attestation、TPM2_、Quote 等平台证明接口的扫描结果全部为 0。

  2. 密钥容器按账号独立隔离:在 JS 调用层(@5321772)中,密钥容器的命名函数 GZi 明确以 accountUuid 作为隔离维度:

function GZi(accountUuid, generation) {
return generation > 0
? `device-registry-${accountUuid}:g${generation}`
: `device-registry-${accountUuid}`;
}

这意味着账号 A 与账号 B 会分别在 TPM 中生成两套完全独立、随机的 ECC P-256 密钥对。由于椭圆曲线公钥的随机性,服务端无法通过比对两个账号导出的公钥(ECCPUBLICBLOB)来推断它们是否生成自同一颗物理 TPM 芯片。

  1. 支持密钥代数轮转与重置:客户端通过 ant-device-key-generation.json 管理密钥代数(generation + 1 即可轮转生成新密钥),且执行系统级 Clear-Tpm 或重装系统后旧密钥即告失效。

因此,该机制的设计目标是持有性证明(Proof-of-Possession)——防止木马窃取本地 Session Token 后在攻击者机器上异地重放请求,而非用于跨账号硬件锁定。


五、 CLI 与 Desktop 机制横向对比与关联面评估#

1. 双端采集与检测特征对照表#

维度Claude Code (CLI 2.1.306)Claude Desktop (GUI 3.1623.0)
打包形态Bun 单文件 EXE(内嵌 25.9 MB 明文 JS)Electron MSIX(40.3 MB JS + 3.8 MB Rust .node)
系统机器码 (MachineGuid 等)含跨平台读取代码,但 hostDetector 零调用(死代码)完全移除(15 条注册表访问路径中无 Cryptography)
物理序列号 / MAC 地址未采集未采集(MAC 仅在内存每隔 3 秒比对以重连 SSH)
CPU 与内存信息仅上报架构(如 x64)完整 CPU 型号、核数、精确内存字节数及内存分档
GPU 与屏幕信息未采集最多 3 块 GPU 的型号/PCI ID/驱动版本 + 分辨率与缩放
开机时间 (boot_time)未采集采集(精确至毫秒,位于 Sentry 上下文)
OS 版本精度基础版本号注册表 BuildLabEx 完整构建标签 + UBR 补丁号
核心持久化标识~/.claude.json 中的 userID / machineID(64 hex)%USERDATA%\ant-did(随机 UUIDv4)
硬件密钥机制软件密钥签名(系统密钥链存储)TPM 2.0 KSP(按账号隔离)或 DPAPI 降级
真实主机名 (hostname)仅在主动开启云会话或设备 MCP 授权时外发作为设备注册的 displayName(截断 255 字符)
Git 远程仓库地址存在灰度采集逻辑(x-claude-code-wiggly-dove)未采集(.git/config 仅出现在清理清单中)
双端身份打通独立运行时无关联GUI 调起 CLI 时按开关注入 sha256(ant-did) 至 userID

2. 多账号关联面强度排序#

综合客户端所有外发数据通道,服务端可观测到的跨账号关联信号按强度由高到低排序如下:

  1. 强关联(确定性主键级):持久化安装标识 ant-did(GUI)与 userID / machineID(CLI)。只要切换账号时未清理本地配置或缓存文件,所有账号在 API 请求头、遥测及 Sentry 事件中均携带完全一致的 UUID / Hex 标识。若开启了 tengu_wiggly_dove 灰度,CLI 下同一项目的 Git Origin URL(jy)同样构成强关联。

  2. 中度关联(网络与会话级):客户端代码不可控但服务端必然可见的源 IP 地址、ASN、代理出口特征、TLS 指纹(JA3/JA4)以及同一次开机周期内毫秒级一致的 device.boot_time。

  3. 弱至中度关联(环境聚类特征):Desktop 端 Sentry 上报的硬件配置组合(CPU 型号 + 多 GPU 型号/驱动版本 + 内存容量 + 屏幕分辨率与缩放)以及 event_logging 中的 BuildLabEx + UBR 系统补丁版本。此类特征无法单独作为封禁主键,但足以在清理了 ant-did 后作为风控模型的辅助聚类因子。

  4. 无关联(客户端不存在该通道):物理硬件序列号、网卡 MAC 地址、Windows MachineGuid、TPM 背书密钥(EK)。


六、 本地自查与遥测控制参考#

若需验证本机客户端生成的持久化标识或控制非必要的数据外发,可通过以下方式进行核查与配置:

1. 查看本地持久化标识与 Sentry 快照#

在 PowerShell 中查看 CLI 端的安装级随机 ID:

Terminal window
$j = Get-Content "$env:USERPROFILE\.claude.json" -Raw | ConvertFrom-Json
$j.machineID
$j.userID

查看 Desktop 端的 ant-did 与已组装的 Sentry 设备上下文快照:

Terminal window
# 读取并解码 ant-did
$didBase64 = Get-Content "$env:LOCALAPPDATA\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\ant-did" -Raw
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($didBase64))
# 查看 Sentry 本地落盘的设备指纹上下文
Get-Content "$env:LOCALAPPDATA\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\sentry\scope_v3.json"

2. CLI 端流量与灰度采集阻断环境变量#

在 CLI(2.1.306)中,可通过环境变量控制遥测与错误上报行为(对应代码中的 WGt() 判定函数):

Terminal window
# 禁用全部遥测(同时会阻断 GrowthBook 远程拉取,使 x-claude-code-wiggly-dove 灰度开关无法激活)
$env:DISABLE_TELEMETRY = "1"
$env:DO_NOT_TRACK = "1"
# 禁用 Sentry / Datadog 错误上报
$env:DISABLE_ERROR_REPORTING = "1"
# 仅保留必要流量(停止 1P event_logging 遥测)
$env:CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1"

(注:启用 DISABLE_TELEMETRY 或 DO_NOT_TRACK 后,由于 GrowthBook 远程特性求值一并被阻断,依赖远程版本协商的附加功能如 Remote Control 云会话可能会受到限制。)