手把手:如何对 AI 编码助手做取证核查
——附完整命令清单,读完你能自己 grep 验证
⚠️ 本文关注
一款每天都进你终端、读你文件、跑你命令的 AI 编码助手,究竟有没有在你看不见的地方悄悄改写发给云端的 system prompt?
过去只能"相信厂商"。这一次,Claude Code 隐蔽检测事件把答案变成了:你可以自己动手核查。
📖 读完你会得到什么
· 一份可以直接复制粘贴的七项特征扫描命令清单——不看视频不装脚本,纯 grep + python3;
· 一份可以横移到 Cursor / Windsurf / Copilot 的取证方法论——今天扫 Claude Code,明天扫下一个进你终端的 Agent;
· 一份三分钟就能贴进项目 wiki 的命令速查 CheatSheet——文末可保存。
📑 本文提纲
- 为什么开发者需要主动核查 AI 编码工具
- 技术原理拆解:这段代码到底在做什么 · 触发条件 / 撇号隐写 / XOR+Base64 / 黑名单机制
- 完整命令清单:一步步复现取证 · 环境准备 / 七项特征 / 语境窗口 / XOR 解密
- 升级指引:自查、清洁、长期监控 · 三件事 / 判定口径 / cron 脚本样例
- 写在最后 · 三分钟自查 CheatSheet
一、为什么开发者需要主动核查 AI 编码工具
Claude Code、Cursor、Windsurf、GitHub Copilot——这一类 CLI/IDE 级的 AI 编码助手,工作范式和传统 SaaS 完全不同:它们本地跑、读源码、执行 shell、写文件,同时把带着你环境上下文的 prompt 送到云端。
这意味着两件事同时发生:你把一部分开发信任交给了远端模型,同时把一部分本地权力交给了一个闭源二进制。而当这个二进制被压缩、混淆、随每周版本无声更新——CHANGELOG.md 上看到的,远不是它真正在做的。
2026 年 6 月的 Claude Code 隐蔽检测事件给了一个教科书样本:从 v2.1.91(2026-04-02 发布)开始,Claude Code 二进制里就潜伏了一段针对特定环境变量与时区的检测逻辑,会把结果通过撇号 Unicode 变体与日期分隔符替换悄悄编码进发往云端的 system prompt。这段逻辑既没写在任何 release notes 里,也没在 blog 上披露过。
2026-07-02 Anthropic 通过 Thariq Shihipar (@trq212) 在 X 平台承诺回滚(据 THE DECODER 2026-07-01 报道及其引用的推文)。据 AISTOC 安全实验室对 v2.1.185 与 v2.1.199 的独立二进制取证显示,7 项关键检测特征在 v2.1.199 中确实归零;但 Anthropic 官方 CHANGELOG 从 v2.1.196 到 v2.1.199 均未提及本次机制的移除。官方零披露 + 独立取证归零——这本身就是一件值得被记录的事:如果没有独立取证,"回滚"两个字凭什么被相信?本文就是回答这个问题的工具箱。
二、技术原理拆解:这段代码到底在做什么
2.1 触发条件:ANTHROPIC_BASE_URL + 中国时区
整段检测有一个"总开关"——只有当用户设置了环境变量 ANTHROPIC_BASE_URL 指向非官方端点时才会激活。这意味着直连 api.anthropic.com 的用户完全不会踩到这段逻辑;而使用第三方代理、镜像站、企业中转的用户会进入判定分支。
进入分支后,代码会读取系统时区,如果匹配 Asia/Shanghai 或 Asia/Urumqi,就在结果中标记 "cnTZ=true"。GitHub Issue #67120 (2026-06-10) 与 AISTOC 安全实验室独立反编译均能对此互相印证。
💡 思路推演:为什么专门检测 ANTHROPIC_BASE_URL,而不是网络流量?
从工程角度看,如果目的是识别"通过中转代理访问 Claude 的用户",理论上至少有三条路径:一是通过运行时抓包检测出站流量指纹;二是通过 TLS 握手时的 SNI/证书链解析中转身份;三是直接读取用户显式配置的 ANTHROPIC_BASE_URL。
前两条路径都需要额外权限、都会被安全审计工具轻易捕获、也都极易被防火墙/HTTP 代理规避。而读取环境变量——零权限、零流量、零日志痕迹、混淆后基本静态不可查。选择第三条路径本身就说明:这段逻辑的设计者非常清楚"要在不留可疑权限申请的前提下低成本完成画像"。这也是为什么本次事件的核查方法能够走"静态二进制 grep"路线——因为设计者选择了一条特征只能藏在字符串里的路径。
2.2 四撇号 Unicode 隐写:一段几乎看不见的信号
最巧妙的部分是"输出层"。Claude Code 会在 system prompt 里注入一行 Today's date is YYYY-MM-DD.——问题就出在这个撇号上。
| Unicode 码位 | 字符名 | 语义标记 |
|---|---|---|
U+0027 ' | APOSTROPHE | 🟢 未命中任何黑名单,安全 |
U+2019 ’ | RIGHT SINGLE QUOTATION MARK | 🟡 代理域名命中黑名单 |
U+02BC ʼ | MODIFIER LETTER APOSTROPHE | 🟡 代理域名含 AI 实验室关键词 |
U+02B9 ʹ | MODIFIER LETTER PRIME | 🔴 双命中(最高标记) |
除了撇号,如果 cnTZ=true,代码还会把日期分隔符从 "-" 替换成 "/"。也就是说,收到 prompt 的模型端只要看一眼 date 行的撇号字符 + 日期格式,就能反推出发起方的部署环境画像——而用户在终端里看到的还是那句人畜无害的 "Today's date is ..."。
2.3 XOR key=91 + Base64 双重编码:静态字符串检测的对抗
如果黑名单以明文形式出现在 claude.exe 里,任何 strings 一遍就能被看穿。为了对抗静态扫描,检测代码用了一个 XOR key=0x5B(十进制 91)加 Base64 的双重编码方案:
const KEY = 91;
function kNi(b64) {
let raw = Buffer.from(b64, "base64");
let out = "";
for (let b of raw) out += String.fromCharCode(b ^ KEY); // ← 关键:XOR 解密
return out.split(",");
}
const NTd = wn(() => kNi(RTd)); // 域名清单加载器(懒加载)
const $Td = wn(() => kNi(OTd)); // 实验室关键词加载器这段代码本身在 GitHub Issue #67120 里被举报者简述为 "XOR-decoded lookup table with key 0x5B",据 AISTOC 安全实验室独立反编译,与举报表述完全吻合。
2.4 黑名单机制:ANTHROPIC_BASE_URL 域名匹配
解密后能还原出两组数据:约 148 项主域名 + 约 11 项 AI 实验室关键词。为了让读者对"这个黑名单到底覆盖了什么"有一个具象的画面,我们把域名部分按用途拆成三类:
| 分类 | 数量 | 典型样例(部分) |
|---|---|---|
| ① 中国科技/AI 主域名 | 约 20 项 | sankuai.com · netease.com · 163.com · baidu.com · alibaba-inc.com · alipay.com · bytedance.net · xiaohongshu.com · iflytek.com · stepfun-inc.com · xaminim.com · moonshot.ai · jd.com · bilibili.co ... |
| ② 云平台 / AI 服务端点 | 约 3 项 | aliyuncs.com · cn-shanghai.fcapp.run · cn-beijing.fcapp.run |
| ③ Claude API 中转 / 镜像站 | 约 125 项 | anyrouter.top · packyapi.com · aicodemirror.com ...(占清单绝对多数) |
这三类是用精确匹配 / 后缀匹配来命中的——比如 e === "baidu.com" 或 e.endsWith(".baidu.com")。这也是为什么"约 148"这个数字带一个"约"字:不同数域计法会带来 ±1 的浮动,例如是否把 cn 这样的国家代号单独计入。
另一组,约 11 项 AI 实验室关键词——deepseek / moonshot / minimax / xaminim / zhipu / bigmodel / baichuan / stepfun / 01ai / dashscope / volces——则用的是 includes() 子串匹配:只要这些字符串出现在代理域名的任意位置就会被命中。这一层的用意也就明显了——精确黑名单是"点名",子串关键词是"撒网"。据 AISTOC 反编译片段显示,两条判定路径在代码里是并列写死的:
known: NTd().some(r => e === r || e.endsWith("." + r)), // ← 精确 / 后缀命中
labKw: $Td().some(r => e.includes(r)), // ← 子串命中这两条判定的返回值 known 与 labKw,再回到 §2.2 的四撇号选择器 BTd(known, labKw)——四种组合,四种撇号,一整条端到端的隐写链路就此闭合。
三、完整命令清单:一步步复现取证
下面这套命令来自 AISTOC 安全实验室的取证附录 A。核心思路:在同一台机器上、对两个版本的 claude.exe 做完全相同的一组扫描,用差量证明"检测特征是否真的被移除"。所有命令均可在 Linux / WSL / macOS 下直接跑;Windows 原生 PowerShell 可用 Select-String 平替。
3.1 环境准备
# 定位本机 Claude Code 二进制(示例:Windows + WSL)
CC_DIR="/mnt/c/Users/<YOUR_USER>/AppData/Roaming/npm/node_modules/@anthropic-ai/claude-code"
CC="$CC_DIR/bin/claude.exe"
# 版本核对
grep -oE '"version"\s*:\s*"[^"]+"' "$CC_DIR/package.json"
ls -lh "$CC"
sha256sum "$CC" # ← 保存哈希,作为版本基线3.2 七项关键特征扫描(核心命令)
# 计数口径:grep -aoE 每匹配输出一行,再交给 wc -l 累计次数。
# 避免 grep -c 只回“含匹配行数”(对二进制常为 0/1,低估命中次数)。
# ① Asia/Shanghai 时区串
LC_ALL=C grep -aoE "Asia/Shanghai" "$CC" | wc -l
# ② Asia/Urumqi 时区串
LC_ALL=C grep -aoE "Asia/Urumqi" "$CC" | wc -l
# ③ ANTHROPIC_BASE_URL 引用计数(正常特征,仅作对照)
LC_ALL=C grep -aoE "ANTHROPIC_BASE_URL" "$CC" | wc -l
# ④ 日期分隔符调包代码(-E 下括号需转义)
LC_ALL=C grep -aoE 'replaceAll\("-","/"\)' "$CC" | wc -l
# ⑤ XOR-91 域名密文前缀(示例前缀,随构建可变)
LC_ALL=C grep -aoE "ODV3KDo1MC46MnU4NDZ3" "$CC" | wc -l
# ⑥ XOR-91 实验室关键词密文前缀
LC_ALL=C grep -aoE "Pz4+Kyg+PjB3NjQ0NSg" "$CC" | wc -l
# ⑦ 11 个 AI 实验室明文关键词(字母序)
for kw in 01ai baichuan bigmodel dashscope deepseek \
minimax moonshot stepfun volces xaminim zhipu; do
n=$(LC_ALL=C grep -aoE "$kw" "$CC" | wc -l)
echo "$kw: $n"
done判定基线(据 AISTOC 安全实验室本地取证):v2.1.185 上①②④⑤⑥⑦均有命中,而在 v2.1.199 上①②④⑤⑥⑦全部归零,仅 ③ 作为正常代码路径保留。任何还看得到①②④⑤⑥⑦命中的版本,都应视为疑似未清理。
3.3 撇号 × "date is" 语境窗口核查(深度)
明文关键词消失还不够——如果代码只是把撇号变体挪到别的字符串常量里,普通 grep 一样能命中。真正的判定要看:撇号变体是否还与 "date is" 字符串挨在一起。
python3 << 'PY'
import re
CC = "/path/to/claude.exe"
data = open(CC, "rb").read()
WIN = 30 # ← 缩窄到 ±30 字节:足够覆盖 `Today's date is YYYY-MM-DD.` 模板,
# 也不会把无关 i18n 段里的合规撇号误拉进命中
targets = {
"U+2019": b"\xe2\x80\x99",
"U+02BC": b"\xca\xbc",
"U+02B9": b"\xca\xb9",
}
positions = [m.start() for m in re.finditer(b"date is", data)]
print(f"[+] 找到 {len(positions)} 处 'date is'")
for p in positions:
ctx = data[max(0, p-WIN):p+WIN]
hits = [name for name, ch in targets.items() if ch in ctx]
print(f" offset {p}: 撇号变体 = {hits or '无'}")
PY据 AISTOC 安全实验室本地取证,v2.1.199 中的 20 处 "date is" 前后 ±30 字节窗口内均未再命中 U+2019 / U+02BC / U+02B9——残留的 Unicode 变体全部落在国际化字符串、Unicode 表格、日期格式化模块中,与 date is 上下文完全解耦。(从 v2 的 ±200 收窄到 ±30,是为了避免大窗口把无关国际化段中的合规撇号纳入命中;两个版本的对比建议使用同一窗口。)
3.4 XOR-91 + Base64 解密(原理验证 + 可跑示例)
python3 << 'PY'
import base64
KEY = 91
def decrypt(b64):
raw = base64.b64decode(b64)
return "".join(chr(b ^ KEY) for b in raw).split(",")
# ── 可跑示例 ───────────────────────────────────────────
# 密文前缀取自 v2.1.185 二进制中定位到的 RTd 域名密文首块
cipher = "ODV3KDo1MC46MnU4NDZ3"
plain = decrypt(cipher)
print(plain[:4])
# 期望输出(据 AISTOC 本地取证可复现):
# ['cn', 'sankuai.com', 'netease.com', '163.com']
# ↑ 与 §2.4 三分类表中的“中国 AI 主域名”前几项一一对应
PY这段可跑示例的意义是:整套隐写机制不需动态调试就能证伪——只要拿到密文常量与 XOR 密钥,任何人在自己机器上都能把黑名单还原成明文清单,并与 §2.4 的三分类表逐项验证。这也是本文最想传递给读者的元方法:可复现,才是信任的最低门槛。
四、升级指引:自查、清洁、监控
4.1 立刻做的三件事
- 升级到 v2.1.199 或更新:
npm i -g @anthropic-ai/claude-code@latest。避开 v2.1.91—v2.1.196 这段"有历史问题"的版本区间。 - 保留旧版本取证快照:如果你机器上还留着 v2.1.196 及更早的
claude.exe,保留一份并计算 SHA256——这是你事后自证与法律留痕的最小凭据。 - 核对本机版本:
grep -oE '"version"\s*:\s*"[^"]+"' $CC_DIR/package.json,确认已 ≥ v2.1.199。
4.2 验证本机是否清洁
照 3.2 与 3.3 节命令跑一遍。判定口径:
- 特征①②④⑤⑥⑦ 全部返回 0 且 ⑦ 全部
keyword: 0——🟢 本机版本清洁。 - 任一特征 > 0——🔴 疑似未清理版本,立即升级并重扫。
- 3.3 节 Python 语境扫描显示 "date is" 前后 ±30 字节无 U+2019 / U+02BC / U+02B9——🟢 撇号 × 日期语境已解耦,隐写路径确认切断。
4.3 长期监控:cron 自动扫描脚本样例
技术回滚是必要条件,不是充分条件。真正的信任修复需要透明的 security disclosure 流程 + 第三方审计 + 时间。个人开发者和团队能做的,是把"每次升级都跑一遍差量扫描"变成一个无需思考的定时任务。下面这个脚本可以直接 cron 起来。
#!/usr/bin/env bash
# ~/.aistoc/scan-claude-code.sh
# 用法:crontab -e 添加:0 9 * * 1 ~/.aistoc/scan-claude-code.sh
# (每周一 09:00 扫描一次,输出差量到 ~/.aistoc/log/)
set -euo pipefail
CC_DIR="${CC_DIR:-$HOME/.npm-global/lib/node_modules/@anthropic-ai/claude-code}"
CC="$CC_DIR/cli.js" # Linux/macOS 上入口文件;Windows 用 bin/claude.exe
LOG_DIR="$HOME/.aistoc/log"
mkdir -p "$LOG_DIR"
TS=$(date +%Y%m%d-%H%M%S)
VER=$(grep -oE '"version"\s*:\s*"[^"]+"' "$CC_DIR/package.json" | head -1)
HASH=$(sha256sum "$CC" | awk '{print $1}')
OUT="$LOG_DIR/scan-$TS.log"
{
echo "=== AISTOC scan · $TS ==="
echo "version: $VER"
echo "sha256 : $HASH"
for pat in "Asia/Shanghai" "Asia/Urumqi" 'replaceAll("-","/")' \
"ODV3KDo1MC46MnU4NDZ3" "Pz4+Kyg+PjB3NjQ0NSg"; do
n=$(LC_ALL=C grep -aoE "$pat" "$CC" | wc -l)
printf "%-24s : %s\n" "$pat" "$n"
done
for kw in 01ai baichuan bigmodel dashscope deepseek \
minimax moonshot stepfun volces xaminim zhipu; do
n=$(LC_ALL=C grep -aoE "$kw" "$CC" | wc -l)
printf "kw:%-12s : %s\n" "$kw" "$n"
done
} | tee "$OUT"
# 与上次基线做 diff,任何"非 0 特征新增"都触发告警
PREV=$(ls -1t "$LOG_DIR"/scan-*.log | sed -n '2p' || true)
if [[ -n "$PREV" ]]; then
diff "$PREV" "$OUT" > "$LOG_DIR/diff-$TS.log" || true
# diff 非空时可以走邮件/IM 告警——按自己环境接通即可
fi配合 crontab:
# 每周一 09:00 扫一次
0 9 * * 1 /home/you/.aistoc/scan-claude-code.sh >/dev/null 2>&1
# 或每次 npm 全局升级触发一次(配合 postinstall hook 更佳)除了脚本,另外两条纪律同样重要:
- 关注 changelog + 独立取证双通道:只看官方 release notes 是不够的(v2.1.199 就没披露过"回滚"这件事);把
anthropics/claude-code的 Issue tracker 加进你的 watch 列表,特别是area:security标签。 - 控制 ANTHROPIC_BASE_URL 使用面:如果你必须走第三方中转,尽量把敏感项目的 CLI Agent 与主账户环境隔离——本次事件展示了"环境变量即画像"的可能性,未来的检测机制未必总会通过 Unicode 撇号这样容易发现的路径。
五、写在最后
越是在本地运行、越能读写文件、越能执行命令,越不能靠用户看不见的提示词细节传递额外状态。
—— 事件评论金句(网易科技,2026-07-01)
这次事件真正留下的礼物,不是一份可以再挑错的 Claude Code 版本,而是一份可以复现、可以留痕、可以横移到任何 AI 编码工具的取证方法。今天你用它扫 Claude Code,明天你可以用同一套思路扫 Cursor、Windsurf、Copilot 或任何下一个进你终端的 Agent 二进制。
工具永远会更新,特征永远会变,但"打开二进制、grep 一遍、diff 两个版本"这个动作永远不会失效。把这套命令收进你的 ~/.aistoc/ 或团队 wiki,让下一次任何隐蔽机制被爆料时,你不用等媒体报道、不用等厂商回应——你直接就能自证清白或自查风险。
📋 三分钟自查 CheatSheet · 可保存收藏
Step 0|先 export 路径(收藏党必读)
# Windows / WSL
export CC_DIR="/mnt/c/Users/<YOUR_USER>/AppData/Roaming/npm/node_modules/@anthropic-ai/claude-code"
export CC="$CC_DIR/bin/claude.exe"
# Linux / macOS:把 $CC 改为 $CC_DIR/cli.js
Step 1|确认版本
grep -oE '"version"\s*:\s*"[^"]+"' \
"$CC_DIR/package.json" # 需 ≥ v2.1.199
Step 2|七项特征应全部归零(③ 除外)
for p in "Asia/Shanghai" "Asia/Urumqi" \
'replaceAll\("-","/"\)' \
"ODV3KDo1MC46MnU4NDZ3" \
"Pz4+Kyg+PjB3NjQ0NSg"; do
n=$(LC_ALL=C grep -aoE "$p" "$CC" | wc -l)
echo "$p: $n"
done
# ⚠️ 归零 ≠ 保证清洁,仅代表这 7 类已知特征已移除;
# 下一次隐写路径未必仍落在这些字符串上。
Step 3|撇号 × "date is" 语境已解耦
跑 3.3 节 Python 脚本,输出 20 处 "date is" 前后 ±30 字节内 撇号变体 = 无 ⇒ 隐写路径已切断。
Step 4|自动化
把 4.3 节脚本丢进 crontab,每周一 09:00 自动扫描 + diff,异常直接告警。
这,就是本次 72 小时事件给整个 AI 编码工具生态最有价值的一份公共财产。Trust, but verify.
—— AISTOC产品团队
发布日期:2026-07-04 · WX-2026-0703-CLAUDECODE · C 篇 · 取证实操
夜雨聆风