Claude Code 正在用隐写方式标记请求

---
title: "Claude Code 正在用隐写方式标记请求"
author: "Thereallo"
source_url: "https://thereallo.dev/blog/claude-code-prompt-steganography"
published_at: "2026-06-30"
fetched_at: "2026-07-02T15:40:43Z"
updated_at: "2026-07-03T12:47:10Z"
language: "zh"
review_status: "reviewed"
---

# Claude Code 正在用隐写方式标记请求

出于隐私原因,我检查了一下 Claude Code。

大多数开发者都会给自己的 harness 很夸张的权限:文件系统、shell、git、浏览器访问,现在甚至还有 computer use。这本来就是它们的价值所在。它们需要足够的上下文,才能做出有用的工作。

但这也意味着,客户端本身也该经得起审视。如果一个 coding agent 能读取你的 repo、运行命令,那么承载这些能力的客户端二进制文件就应该本分、可预期(比如 pi harness)。

于是我看了一下本地安装的 Claude Code(2.1.196)。

## 标记

在 Claude Code 二进制文件里,有一个函数会修改插入到 system prompt 里的当前日期字符串。

正常字符串长这样:

```
Today's date is 2026-06-30.
```

Claude Code 可以静默修改两件事:

1. `Today's` 里的撇号
2. 日期分隔符,从 `-` 改成 `/`

下面是相关代码,我从压缩后的 bundle 里清理了一下:

```javascript
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 `Today${apostrophe}s date is ${renderedDate}.`;
}
```

这就是 prompt 隐写(prompt steganography),也就是把数据藏在明面上的一种技术。

可见的句子读起来仍然像一个普通日期。模型和用户看到的,只是一段平平无奇的文本。原始请求里却藏着一个标记。

## 检查

```javascript
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`,也就是 Claude Code 的 API base URL 覆盖项。

然后它会检查:

- 系统时区是不是 `Asia/Shanghai` 或 `Asia/Urumqi`
- API base URL 的 hostname 是否匹配解码后的域名列表
- hostname 是否包含特定 AI lab 关键词

时区检查会把:

```
2026-06-30
```

改成:

```
2026/06/30
```

hostname 检查会修改撇号:

| 条件 | 撇号 |
| --- | --- |
| 正常 | `'` |
| 已知域名 | `\u2019` |
| Lab 关键词 | `\u02BC` |
| 已知域名 + Lab 关键词 | `\u02B9` |

这些变化在视觉上非常细微,在大多数等宽字体里你根本不会注意到。

## 混淆列表

域名和关键词列表以 base64 字符串保存,并用 key `91` 做 XOR 解码。

```javascript
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(",");
}
```

解码后的 lab 关键词列表是:

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

解码后的域名列表要大得多。里面包含中国公司域名、AI 公司域名,以及大量代理、reseller、gateway 域名。

一些例子:

```
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](https://cdn.thereallo.dev/blog/assets/cc-domains.js)

## 它是如何传进去的

构建 agent context 时会用到这个日期函数:

```javascript
{
  ...userEmail && {
    userEmail: `The user's email address is ${userEmail}.`
  },
  ...attachedProject && {
    attachedProject
  },
  currentDate: Vla(GSe())
}
```

所以这个标记会被放进发给模型的 system context 里。(Anthropic 很可能会在后端解析它。)

## 我的环境显示什么

我安装的二进制文件由 Anthropic 签名:

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

我当前 shell 里没有设置 `ANTHROPIC_BASE_URL`,我的时区是:

```
Asia/Hong_Kong
```

所以在我的机器和当前环境下,这条路径会生成正常撇号和正常的 `YYYY-MM-DD` 日期字符串。

## 令人担忧之处

Anthropic 可能是想检测 API 转售商(reseller)、未经授权的 Claude Code gateway,以及模型“蒸馏攻击”(distillation attack)流水线。一个指向已知转售商域名的自定义 `ANTHROPIC_BASE_URL` 是有用信号。包含 `deepseek` 或 `zhipu` 的 hostname 也是有用信号。

这部分说得通,但实现方式很奇怪。

CC 会用近乎不可见的 Unicode 标记静默修改 system prompt。它把 proxy / gateway 分类编码进一个看起来像普通英文的句子里。它把域名列表藏在 XOR 和 base64 后面。这不是恶意功能,但对于一个需要用户信任的开发者工具来说,这是一个奇怪的选择。

Coding agent 本来就已经越过了一条让人紧张的边界。它们可以检查代码,可能不小心把 secret 摘出来,可以运行命令、安装包、编辑文件,还能在**你的本地机器**上 push commit。大多数开发者接受这一点,是因为生产力收益值得承担风险。

开发者真正愿意信任它,靠的是它在这些地方本分、可预期。

如果客户端想检测自定义 API gateway,它可以明说。它可以发送一个明确的遥测(telemetry)字段,并配上文档。它可以把规则摆明。它可以把这个行为写进发布说明。

把信号藏在 system prompt 里,会让其他所有隐私声明都更难令人相信。

## 实际影响

对大多数用户来说,这条逻辑大概率不会被触发。

如果你使用官方 Anthropic API endpoint,`Crt()` 会提前返回。如果没有设置 `ANTHROPIC_BASE_URL`,`Crt()` 会提前返回。如果你使用的是普通配置,日期 prompt 会保持正常。

真正值得注意的是那些让 CC 走自定义 base URL 的场景,包括:

- 内部 gateway
- 本地 proxy
- 模型 router
- 转售商
- 研究设置

在这种情况下,Claude Code 会分类 hostname,并把结果编码进 prompt。

绕过也很简单。换 hostname、换时区、patch 二进制、在外面再套一层。任何真正有心绕过的人都能让这个信号失效。

所以,这个功能主要惩罚的,恰恰是那些最容易被指纹识别的普通开发者:他们做的事奇怪,但正当。

## 想法

我觉得这完全可以明说。

开发者工具可以执行使用条款。API 提供商可以检测滥用。公司可以保护自己的模型。

但当一个拥有文件系统和 shell 访问权限的工具,开始把分类信号藏进近乎不可见的 prompt 标点里时,它就应该接受认真审视。

信任来自那些本分、可预期的细节。