最近围绕 Claude Code 有一轮争议:有逆向分析者称,某些版本的 Claude Code 会在特定条件下读取本机环境信息,并把检测结果编码进发往服务端的 system prompt 里。这个机制不是额外上传一个明显的 telemetry 字段,而是修改一行看起来很普通的日期提示,例如 Today's date is 2026-06-30.。
公开讨论里最值得看的,不是“封号”这个结果,而是检测流程本身。它展示了一个本地 AI 编程工具如何在不增加额外网络请求的情况下,把客户端环境特征夹带进正常请求。对开发者来说,这比单纯的账号风险更值得警惕。
本文只整理目前公开逆向文章和社区讨论中反复出现的技术细节。由于这类分析依赖具体版本和逆向结果,下面内容应理解为“公开报告所称的实现逻辑”,不等同于官方完整说明。
先把流程说白
这套逻辑如果用一句话概括,就是:先看你有没有走自定义 API 地址,再看这个地址像不像特定名单里的服务,再看你电脑时区,最后把判断结果藏进一句日期提示里。
按公开分析里的说法,链路大概是这样:
- Claude Code 启动或发请求前,读取
ANTHROPIC_BASE_URL。 - 如果这个变量不存在,或者仍然指向官方默认地址,后面的特殊标记逻辑可能不会触发。
- 如果它指向自定义 endpoint,就解析 URL,取出 hostname。
- 客户端把 hostname 拿去和内置名单、关键词规则做匹配。
- 同时读取本机时区,判断是否是
Asia/Shanghai或Asia/Urumqi这类目标时区。 - 客户端不新增单独字段,而是修改 system prompt 里的日期句子。
- 服务端收到请求后,通过日期分隔符和撇号码点,读出客户端环境标记。
这个过程最绕的地方在第 6 步。它不是写一个很明显的字段,比如:
|
|
而是把信息塞进这种看起来很正常的文本里:
|
|
普通人扫一眼只会觉得这是日期。真正传递信息的,是日期里的 / 或 -,以及 Today's 中间那个看起来几乎一样的撇号。
如果写成伪代码,逻辑大概像这样:
|
|
这段伪代码的重点不是具体函数名,而是顺序:先读本地环境,再做本地判断,最后把判断结果写回 prompt 文本。用户看到的是一次普通模型请求,服务端看到的则是带状态的请求。
每一步到底检测了什么
把流程拆细一点,可以看到每一步拿到的信息并不复杂,但组合起来就能形成一个比较明确的环境画像。
| 步骤 | 客户端读取什么 | 用来判断什么 | 结果写到哪里 |
|---|---|---|---|
| 读取环境变量 | ANTHROPIC_BASE_URL |
是否使用自定义 API endpoint | 决定是否进入后续逻辑 |
| 解析 URL | hostname | endpoint 属于哪类域名 | 影响 apostrophe 码点 |
| 匹配名单 | 域名后缀、关键词 | 是否命中特定服务或 AI 实验室相关关键词 | 影响 apostrophe 码点 |
| 读取系统时区 | 本机 timezone | 是否属于特定地区时区 | 影响日期分隔符 |
| 改写提示词 | 日期句子 | 把前面判断结果编码进去 | system prompt |
所以它不是靠单一信号判断。ANTHROPIC_BASE_URL 只是入口,hostname 和时区才是后续分层依据,最后的 system prompt 才是承载结果的地方。
触发点:ANTHROPIC_BASE_URL
公开分析里提到的第一步,是读取环境变量 ANTHROPIC_BASE_URL。
这个变量通常用于让 Claude Code 不直接请求官方 api.anthropic.com,而是走自定义 API endpoint、企业网关、代理服务或兼容 Anthropic API 的中转服务。也就是说,它本身不是恶意配置,很多团队会因为网络、合规、审计、统一鉴权或成本管理而使用自定义网关。
据公开逆向分析称,相关逻辑并不是对所有请求无条件触发,而是在检测到配置了非默认 API base URL 后,才进入后续判断。这个设计很关键:它把“普通官方直连用户”和“使用自定义 endpoint 的用户”先分开,再对后者做更细的环境识别。
从流程上看,大致可以拆成三层:
- 是否设置了
ANTHROPIC_BASE_URL。 - 这个 URL 的 hostname 是否命中特定域名或关键词名单。
- 本机时区是否属于特定地区。
这也是为什么争议会集中在 Claude Code 这类本地开发工具上。它运行在用户机器里,天然能读取本机环境变量、时区、配置文件和 shell 上下文。
第一步:提取自定义 endpoint 的 hostname
如果存在 ANTHROPIC_BASE_URL,客户端可以把它解析成 URL,并取出 hostname。
例如:
|
|
真正参与匹配的通常不是完整 URL,而是:
|
|
这样做有两个效果。
第一,路径、query string、具体接口版本都不重要,只看域名主体。第二,匹配逻辑可以复用一份域名或关键词名单,不必关心不同服务商把 API path 设计成什么样。
公开讨论里提到,这份名单并不是明文平铺在代码里,而是经过混淆或编码存储,需要运行时解码后再匹配。这样的处理未必一定代表恶意,但它确实降低了普通用户通过搜索字符串发现规则的可能性。
第二步:检查系统时区
另一个被反复提到的信号,是系统时区。
公开分析称,检测逻辑会关注类似下面的时区:
|
|
时区是一个很微妙的信号。它不需要发起网络请求,也不需要读取 IP 地址,却能粗略反映用户所在地区或系统长期使用习惯。很多开发者即使切换了网络出口,也不会修改电脑时区,因为改时区会影响日历、日志、构建时间戳和日常使用。
如果客户端只看 IP,用户可能会使用代理、企业出口或云环境;如果同时看本机时区,就多了一层本地环境判断。
第三步:匹配域名名单和关键词
除了时区,公开分析还提到两类 hostname 匹配:
- 是否命中特定域名、域名后缀或服务商名单。
- 是否包含与 AI 实验室、模型服务或相关公司有关的关键词。
这类匹配的目标,可能不是简单判断“是不是中国用户”,而是把不同风险类型分层:普通自定义网关、地区相关 endpoint、AI 实验室相关 endpoint、二者同时命中。
这也是后续编码需要多个状态的原因。如果只想表达“命中/未命中”,一个二值标记就够了;但公开分析里提到的 Unicode 字符替换,能表示更多组合状态。
关键手法:把结果写进 system prompt
争议最大的地方,是检测结果并不是作为显式字段上传,而是被编码进 system prompt 中的日期句子。
Claude Code 请求里本来就会包含类似这样的系统信息:
|
|
公开分析称,客户端会在发送请求前对这句话做两类修改。
第一类是日期分隔符。如果检测到特定时区,日期可能从:
|
|
变成:
|
|
第二类是替换 Today's 里的 apostrophe。肉眼看起来都是一个撇号,但 Unicode 码点不同。例如:
|
|
它们在显示层面非常接近,但在程序眼里是完全不同的字符。服务端只要检查 Today 和 s date is 中间那个字符的码点,就能读出客户端编码进去的状态。
更直观地看,可以把它理解成一张“暗号表”:
| 显示效果 | Unicode | 大致含义 |
|---|---|---|
' |
U+0027 |
普通 ASCII apostrophe,未命中特定条件 |
’ |
U+2019 |
命中某类域名或后缀名单 |
ʼ |
U+02BC |
命中 AI 实验室相关关键词 |
ʹ |
U+02B9 |
多个条件同时命中 |
上面这张表不是为了教人绕过检测,而是说明它为什么不容易被肉眼发现:四个字符都像撇号,但码点完全不同。日志、终端、网页字体一渲染,差异会更不明显。
这就是为什么很多人把它称为 prompt steganography,也就是把信息藏进看起来正常的提示词文本里。
为什么这种标记比较隐蔽
这种做法隐蔽,主要有三个原因。
第一,它不需要额外请求。网络抓包时,你不会看到一个单独的“上报环境信息”接口,请求仍然是正常的模型调用。
第二,它不需要明显字段。即使你检查 JSON,也可能只看到普通 system prompt。除非逐字符比较 Unicode 码点,否则很容易忽略撇号差异。
第三,它利用了用户对系统提示词的低关注度。开发者通常会关注自己输入的 prompt、工具调用参数、token 消耗和模型输出,很少逐字检查客户端自动拼进去的系统上下文。
从工程角度看,这种机制很聪明;从信任角度看,它也很敏感。因为用户授权 Claude Code 运行在本地终端里,是为了让它读写代码、执行命令、协助开发,而不是为了让本地环境特征被隐藏编码进请求。
服务端如何读取这类信息
如果公开分析属实,服务端读取这类标记并不复杂。
它只需要在接收请求后检查两件事:
- 日期使用
-还是/。 Today's中间的 apostrophe 是哪个 Unicode 字符。
换成更接地气的说法:客户端发出去的是一张“正常表格”,但日期格式和撇号字体里夹了两个小纸条。
例如正常请求可能长这样:
|
|
命中特定时区后,可能变成:
|
|
命中某类 endpoint 后,撇号可能变成另一个 Unicode 字符。肉眼看到的仍然像:
|
|
但程序读到的不是同一个 '。服务端不需要理解整句话,只要取出两个位置的字符,就能还原标记状态。
然后就能把请求归类为不同状态。例如:
- 没有命中特定名单。
- 命中某类域名名单。
- 命中 AI 实验室相关关键词。
- 同时命中多个条件。
- 系统时区属于特定地区。
也就是说,真正的“上报字段”并不叫 region、proxy 或 risk_flag,而是藏在自然语言系统提示词里的字符差异。
这和普通风控有什么区别
服务商做风控并不奇怪。API 滥用、账号转售、模型蒸馏、异常并发和违反地区政策,都是现实问题。问题在于透明度和边界。
普通风控通常更容易被理解:登录 IP、支付地区、请求频率、设备信息、组织账号、API key 使用模式。这些信号虽然也敏感,但用户大致能预期平台会用它们做安全判断。
而本次争议点在于:如果客户端确实把本机时区、自定义 endpoint 命中结果等信息隐藏编码进 system prompt,用户很难从产品界面、请求字段或 release notes 里知道这件事。对一个拥有文件系统和 shell 权限的开发工具来说,这会直接影响信任。
开发者可以关注什么
这类事件给开发者的启发,不是简单地“不要用某个工具”,而是要重新看待本地 Agent 的信任边界。
可以关注几件事:
- 本地 CLI 是否会读取环境变量、配置文件、时区、系统语言等环境信息。
- 自动拼接的 system prompt 是否可查看、可导出、可审计。
- 请求发送前的最终 payload 是否能被用户检查。
- release notes 是否说明了新增的检测、风控或遥测逻辑。
- 企业网关是否能记录和审计上游请求内容。
- 本地工具是否提供关闭遥测或敏感环境读取的选项。
如果一个 AI 编程工具能读写项目文件、执行 shell 命令、访问 git、读取环境变量,那么它本质上已经接近“高权限本地代理”。这类工具的透明度标准,应该比普通聊天网页更高。
结论
这次 Claude Code 请求水印争议,真正值得讨论的不是某个账号是否被封,而是客户端如何把环境信息编码进正常请求。
按公开逆向分析的说法,这套流程大致是:读取 ANTHROPIC_BASE_URL,解析 hostname,匹配域名和关键词名单,检查系统时区,再通过日期分隔符和 Unicode apostrophe 把结果写进 system prompt。整个过程不需要额外网络请求,也不需要新增显式字段。
如果这只是反滥用风控的一部分,服务商也应该用更透明的方式说明边界。开发者给本地 Agent 的权限越大,越需要知道它到底读取了什么、改写了什么、发送了什么。
AI 编程工具的能力越强,信任问题就越不能只靠“默认相信”解决。