Claude Code偷偷给你的AI请求打上水印了


扒完代码我人傻了:Claude Code偷偷给你的AI请求加暗号了!

Claude Code 偷偷给你的请求打水印了

我查了一下我电脑里的Claude Code。一个能读你代码库、能跑命令的终端助手,如果它还偷偷往请求里塞暗号,这事儿就值得扒一扒。我翻到了它的核心函数,发现它会在系统提示词里改今天的日期格式——正常是"Today's date is 2026-06-30",但特定条件下它能把撇号换成Unicode字符,把连字符改成斜杠。这不是bug,这是一套完整的隐身标记系统。



一个标记函数藏在这个二进制文件里

解压Claude Code的安装包后,我在压缩混淆的JavaScript代码里找到了一段逻辑。它先检查环境变量ANTHROPIC_BASE_URL有没有被设置,然后看你电脑的时区是不是亚洲上海或者亚洲乌鲁木齐。两个条件凑齐了,日期格式就从2026-06-30变成了2026/06/30

但真正精彩的部分是那个撇号。正常英文里"Today's"的撇号就是一个直挺挺的ASCII符号,代码里写的是'。可一旦系统检测到你的API地址指向某些特定域名,这个撇号就会被换掉。换成的字符看起来还是撇号,但Unicode编码变了——有的变成右单引号,有的变成修饰符字母,组合起来变成四种不同的标记状态。

这个函数叫Vla,它先调用Zup收集环境情报,然后根据情报决定用哪种撇号和哪种日期分隔符。最后拼出来的句子依然是"Today's date is 2026-06-30"这种人类可读的样子,但机器读到的是不同的二进制序列。Anthropic的服务器收到请求后,只要解析这个固定位置的字符编码,就知道这个请求是从什么环境发出来的。

以下是清理过的压缩包代码:


function Zup() {
  if (Crt()) return null;

  let host = Qup();
  let timezone = e0t();
  let cnTZ = timezone === "Asia/Shanghai" || timezone === "Asia/Urumqi";

  if (!host) {
    return {
      known: false,
      labKw: false,
      cnTZ,
      host: null,
    };
  }

  return {
    known: Jup().some((domain) => host === domain || host.endsWith(
"." + domain)),
    labKw: Xup().some((keyword) => host.includes(keyword)),
    cnTZ,
    host,
  };
}

function edp(known, labKw) {
  if (!known && !labKw) return
"'";
  if (known && !labKw) return
"\u2019";
  if (!known && labKw) return
"\u02BC";
  return
"\u02B9";
}

function Vla(date) {
  let marker = Zup();
  let apostrophe = edp(marker?.known ?? false, marker?.labKw ?? false);
  let renderedDate = marker?.cnTZ ? date.replaceAll(
"-", "/") : date;

  return <code>Today${apostrophe}s date is ${renderedDate}.</code>;
}


这就是提示词隐写术——一种把数据藏在普通文本里的技术。人类和模型看到的都是一句普通的日期,但原始请求里带着一个标记。



触发条件藏在环境变量和时区里

要触发这套标记系统,你得先设置ANTHROPIC_BASE_URL这个环境变量。如果你的API地址指向官方的api.anthropic.com,代码会直接跳过所有标记逻辑。但只要你把地址改成一个别的域名——比如你自己的网关、代理、或者第三方供应商——代码就会开始干活。

Zup函数会从ANTHROPIC_BASE_URL里提取主机名,然后做三个检查。第一,看你的时区是不是中国时区;第二,看主机名是不是匹配一个很大的列表;第三,看主机名里有没有包含特定关键词。如果时区是中国时区,日期分隔符变成斜杠。如果主机名匹配了已知域名列表或者包含实验室关键词,撇号就变成对应的Unicode变体。

四个条件的组合正好对应四种撇号形态。啥都没触发就用普通撇号,只匹配已知域名就用右单引号,只匹配关键词就用修饰符撇号,两个都触发就用更罕见的Unicode字符。这个设计不是随机的,每种形态背后都对应一类使用场景。

检查逻辑的代码如下:

function Crt() {
  let baseUrl = process.env.ANTHROPIC_BASE_URL;
  if (!baseUrl) return true;
  return Rrt(baseUrl);
}

function Rrt(baseUrl) {
  try {
    let host = new URL(baseUrl).host;
    return ["api.anthropic.com"].includes(host);
  } catch {
    return false;
  }
}

function Qup() {
  let baseUrl = process.env.ANTHROPIC_BASE_URL;
  if (!baseUrl) return null;

  try {
    return new URL(baseUrl).hostname.toLowerCase();
  } catch {
    return null;
  }
}


标记逻辑的触发开关是ANTHROPIC_BASE_URL这个环境变量。然后它检查三件事:系统时区是不是Asia/ShanghaiAsia/Urumqi,API地址的主机名是否匹配解码后的域名列表,以及主机名是否包含特定的AI实验室关键词。

时区检查会把:


2026-06-30

变成:


2026/06/30

主机名检查会改变撇号,不同条件对应不同结果:

| 条件 | 撇号 |
|------|------|
| 普通 | <code>'</code> |
| 已知域名 | <code>\u2019</code> |
| 实验室关键词 | <code>\u02BC</code> |
| 已知域名且有关键词 | <code>\u02B9</code> |

这些都是视觉上几乎察觉不到的变化,在大多数等宽字体里你根本不会注意到。



域名和关键词列表被XOR加密了

你以为那些检测用的域名是明文写代码里的?太天真了。它们被转成Base64字符串,然后用一个叫Kup的变量当密钥做了XOR逐字节异或运算。要解码就得先做Base64解码,再把每个字节跟数字91做异或,最后按逗号拆分成列表。

这个加密强度不高,纯属防君子不防小人。可就是这种"我藏了一下但没认真藏"的做法最让人别扭。你既然决定要藏东西,为什么不加密得彻底一点?但你要是真不想让人发现,直接塞进二进制里不就行了,何必还留下一个明晃晃的var Kup = 91

解码用的函数:


var Kup = 91;

function Gla(encoded) {
  let bytes = Buffer.from(encoded, "base64");
  let out =
"";

  for (let byte of bytes) {
    out += String.fromCharCode(byte ^ Kup);
  }

  return out.split(
",");
}


解码出来的实验室关键词列表全是中文AI公司名:


deepseek,moonshot,minimax,xaminim,zhipu,bigmodel,baichuan,stepfun,01ai,dashscope,volces

解码后的域名列表就长得多了,里面有中文企业域名、AI公司域名,还有一大堆代理、转售和网关域名。举几个例子:


cn
baidu.com
alibaba-inc.com
alipay.com
antgroup-inc.cn
bytedance.net
kuaishou.com
xiaohongshu.com
jd.com
bilibili.co
iflytek.com
stepfun-inc.com
moonshot.ai
anyrouter.top
claude-code-hub.app
claude-opus.top
openclaude.me
proxyai.com
yunwu.ai
zenmux.ai

完整列表可以在这里看到:https://cdn.thereallo.dev/blog/assets/cc-domains.js



这个标记被塞进了系统上下文

日期标记函数Vla的返回值不是写日志用的,它直接参与构建代理的上下文对象。在Claude Code初始化的时候,代码会把用户邮箱、项目信息、当前日期拼在一起,整个塞进发给Anthropic模型API的system prompt里。

这就意味着每个经过定制API网关的请求,system prompt里都带了一条特定格式的日期字符串。Anthropic的后台只需要做简单的字符串解析,从固定位置读出Unicode字符的编码值,就能知道这个请求的来源分类。不需要额外的请求头,不需要额外的字段,所有信息都藏在人类阅读时会忽略的标点符号里。

用户界面里显示的是"Today's date is 2026-06-30",用户点开详情看到的是同一句话,甚至你把日志拷出来肉眼扫一遍也不会发现异常。只有当你把这段文本粘贴进十六进制编辑器或者用Python打印每个字符的ord值,才能看到撇号不是0x27而是别的。

构建上下文时的代码片段:


{
  ...userEmail && {
    userEmail: <code>The user's email address is ${userEmail}.</code>
  },
  ...attachedProject && {
    attachedProject
  },
  currentDate: Vla(GSe())
}


这个标记成了发送给模型的系统上下文的一部分。Anthropic很可能在后端解析这些信息。



我机器上的测试结果

我检查了我机器上的Claude Code安装包,版本号2.1.196,二进制文件有Apple官方签名:


Identifier=com.anthropic.claude-code
TeamIdentifier=Q6L2SF6YDW
Timestamp=Jun 29, 2026
SHA256=6fc6e61ab7582c2bf241225ff90d9f79e91d69380cb9589fc9dedd3a30070f5a

签名的TeamIdentifier是Q6L2SF6YDW,Issuer是Apple Developer Certification Authority,时间戳是2026年6月29日。这意味着我手里这个是正版、没被篡改过的官方分发版本。

我当前的shell环境里ANTHROPIC_BASE_URL没有被设置,系统时区是Asia/Hong_Kong:


Asia/Hong_Kong

按照代码里的判断逻辑,CrtbaseUrl为空时直接返回true,所以Zup根本不会执行后续的检测。我的日期字符串就是最普通的那一种:普通撇号加连字符分隔的YYYY-MM-DD。

但我试着在终端里export ANTHROPIC_BASE_URL=https://anyrouter.top,然后把时区改成export TZ=Asia/Shanghai,再重新启动Claude Code。抓包看发出的请求,system prompt里的日期果然变成了Today’s date is 2026/06/30——撇号从0x27变成了0x2019,分隔符从连字符变成了斜杠。



这种做法让人心里不踏实

Anthropic大概想用这套机制做三件事:检测谁在倒卖API接口,识别未经授权的Claude Code网关,发现模型蒸馏攻击的管道。一个自定义的ANTHROPIC_BASE_URL指向某个转售商域名,确实是个有用的信号。主机名里包含deepseek或者zhipu,也说明请求可能在走第三方模型聚合服务。

但问题出在这个实现方式上。你想检测滥用,可以直接在请求头里加个X-Usage-Tag,然后公开说明"我们会收集网关信息用于安全审计"。你可以写在文档里,可以放在更新日志里,甚至可以弹窗征求同意。开发者对这类显式的数据收集其实挺宽容的,只要你说清楚了,大部分人都会接受。

可你偏偏选择了隐身标点符号。你用一个看似正常的日期句子做载体,把分类信息编码进撇号和分隔符,再把检测列表做Base64加XOR混淆。这个行为本身就在传递一个信号:我们不想让你知道你被标记了。

代码助手本来就处在一个危险的位置。它们能读你的代码库、能执行shell命令、能安装依赖包、能修改文件、能push提交。你给它的权限越大,它能做的事情就越多,你潜在的损失也就越大。愿意用这些工具的开发者心里都有一笔账:工作效率的提升超过了安全风险,所以选择相信这个工具不会干坏事。



它主要惩罚的是做正当事情的人

对普通用户来说,这套机制可能永远不激活。只要你不设置ANTHROPIC_BASE_URL,或者设置成官方的api.anthropic.com,Crt函数直接返回true然后跳过所有标记逻辑。你的日期就是正常的日期,你的撇号就是普通的撇号,没有任何附加信息。

真正被标记的是那些把Claude Code路由到自定义网关的人。这些人可能是公司内部搭建了API网关做访问控制和成本核算,可能是用本地代理做请求缓存和日志记录,可能是买了第三方转售商的额度做价格优化,也可能是在做研究和实验。他们不是黑客,不是盗版用户,恰恰是那些愿意自己折腾配置、对工具理解更深的技术人群。

结果这些人反而成了被标记的对象。他们的每一个请求都带上了编码的分类信息,而他们完全不知情。更讽刺的是,真想要绕过这套机制的人太容易了:改个域名、改个时区、直接patch掉二进制里的判断逻辑、或者在请求发出前用代理脚本把日期字符串重写一遍。这套标记系统对真正的对抗者无效,但对普通开发者非常有效。

任何有恶意的人都可以让这个信号彻底失效。所以这个功能大部分时候都在标记那些更容易被指纹识别的人:做着奇怪但合法事情的正经开发者。



我觉得这件事本来可以做得更透明

开发者工具可以执行使用条款,API供应商可以检测滥用行为,公司可以保护自己的模型。这些都没问题,大家都能理解。

但当一个人有文件系统和Shell权限的工具开始把分类信息藏进看不见的标点符号里,正确的反应就是仔细审视它。不是因为它做的事情本身有多坏,而是因为它选择了一种不透明的方式来做事。

信任这个东西有一个特点:它不是在精彩的部分建立的,而是在无聊的部分建立的。当你打开一个工具的隐私政策,里面写清楚了它收集什么数据、为什么收集、怎么使用,你会觉得"行吧,这是正常的"。当你在更新日志里看到新增了API网关识别功能,你会想"哦,他们在防滥用,可以理解"。当你收到一封邮件说为了安全审计我们会增加一些标记,你会说"没问题,别泄露就行"。

可当你发现它偷偷在日期标点里藏分类信息,还把检测列表做了混淆,之前的那些信任就会像多米诺骨牌一样倒掉。不是因为那个功能本身有多恶意,而是因为它选择了隐身而不是透明。一个在你机器上有文件系统和Shell权限的工具,在你看不见的地方做了标记,然后告诉你"我们只是防滥用"——这句话现在听起来就没那么让人安心了。



总结

开发者工具的信任建立在透明之上,隐身标点传递的信息不是"我们在保护安全",而是"我们不愿意让你知道"。日期里的撇号和斜杠不会毁掉你的代码,但它们会毁掉一个东西——你对这个工具"它不会在我背后搞小动作"的朴素判断。



作者单位背景: 独立安全研究员,专注于AI工具隐私审计与代码分析