Claude Code 被曝在請求裡夾暗號:一個撇號就能標記你的環境

用人話拆解 Claude Code 請求水印爭議:它可能如何從 ANTHROPIC_BASE_URL、時區和域名清單一路判斷,再把結果藏進一句日期提示裡。

最近圍繞 Claude Code 有一輪爭議:有逆向分析者稱,某些版本的 Claude Code 會在特定條件下讀取本機環境資訊,並把檢測結果編碼進發往服務端的 system prompt 裡。這個機制不是額外上傳一個明顯的 telemetry 欄位,而是修改一行看起來很普通的日期提示,例如 Today's date is 2026-06-30.

公開討論裡最值得看的,不是「封號」這個結果,而是檢測流程本身。它展示了一個本地 AI 編程工具如何在不增加額外網路請求的情況下,把客戶端環境特徵夾帶進正常請求。對開發者來說,這比單純的帳號風險更值得警惕。

本文只整理目前公開逆向文章和社群討論中反覆出現的技術細節。由於這類分析依賴具體版本和逆向結果,下面內容應理解為「公開報告所稱的實作邏輯」,不等同於官方完整說明。

先把流程說白

這套邏輯如果用一句話概括,就是:先看你有沒有走自訂 API 地址,再看這個地址像不像特定清單裡的服務,再看你電腦時區,最後把判斷結果藏進一句日期提示裡。

按公開分析裡的說法,鏈路大概是這樣:

  1. Claude Code 啟動或發請求前,讀取 ANTHROPIC_BASE_URL
  2. 如果這個變數不存在,或者仍然指向官方預設地址,後面的特殊標記邏輯可能不會觸發。
  3. 如果它指向自訂 endpoint,就解析 URL,取出 hostname。
  4. 客戶端把 hostname 拿去和內建清單、關鍵字規則做匹配。
  5. 同時讀取本機時區,判斷是否是 Asia/ShanghaiAsia/Urumqi 這類目標時區。
  6. 客戶端不新增單獨欄位,而是修改 system prompt 裡的日期句子。
  7. 服務端收到請求後,透過日期分隔符和撇號碼點,讀出客戶端環境標記。

這個過程最繞的地方在第 6 步。它不是寫一個很明顯的欄位,比如:

1
2
3
4
{
  "region": "CN",
  "proxyMatched": true
}

而是把資訊塞進這種看起來很正常的文字裡:

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

普通人掃一眼只會覺得這是日期。真正傳遞資訊的,是日期裡的 /-,以及 Today's 中間那個看起來幾乎一樣的撇號。

如果寫成偽代碼,邏輯大概像這樣:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
baseUrl = readEnv("ANTHROPIC_BASE_URL")

if baseUrl is empty or baseUrl is official api.anthropic.com:
    send normal system prompt
else:
    hostname = parseHostname(baseUrl)
    domainHit = matchDomainList(hostname)
    labHit = matchAiLabKeywords(hostname)
    timezoneHit = localTimezone in ["Asia/Shanghai", "Asia/Urumqi"]

    dateText = buildDateText()
    dateText = encodeTimezone(dateText, timezoneHit)
    dateText = encodeDomainStatus(dateText, domainHit, labHit)

    send system prompt with dateText

這段偽代碼的重點不是具體函式名,而是順序:先讀本地環境,再做本地判斷,最後把判斷結果寫回 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 的使用者」先分開,再對後者做更細的環境識別。

從流程上看,大致可以拆成三層:

  1. 是否設定了 ANTHROPIC_BASE_URL
  2. 這個 URL 的 hostname 是否命中特定域名或關鍵字清單。
  3. 本機時區是否屬於特定地區。

這也是為什麼爭議會集中在 Claude Code 這類本地開發工具上。它運行在使用者機器裡,天然能讀取本機環境變數、時區、設定檔和 shell 上下文。

第一步:提取自訂 endpoint 的 hostname

如果存在 ANTHROPIC_BASE_URL,客戶端可以把它解析成 URL,並取出 hostname。

例如:

1
https://example-gateway.com/v1

真正參與匹配的通常不是完整 URL,而是:

1
example-gateway.com

這樣做有兩個效果。

第一,路徑、query string、具體介面版本都不重要,只看域名主體。第二,匹配邏輯可以復用一份域名或關鍵字清單,不必關心不同服務商把 API path 設計成什麼樣。

公開討論裡提到,這份清單並不是明文平鋪在程式碼裡,而是經過混淆或編碼儲存,需要執行時解碼後再匹配。這樣的處理未必一定代表惡意,但它確實降低了普通使用者透過搜尋字串發現規則的可能性。

第二步:檢查系統時區

另一個被反覆提到的信號,是系統時區。

公開分析稱,檢測邏輯會關注類似下面的時區:

1
2
Asia/Shanghai
Asia/Urumqi

時區是一個很微妙的信號。它不需要發起網路請求,也不需要讀取 IP 地址,卻能粗略反映使用者所在地區或系統長期使用習慣。很多開發者即使切換了網路出口,也不會修改電腦時區,因為改時區會影響日曆、日誌、構建時間戳和日常使用。

如果客戶端只看 IP,使用者可能會使用代理、企業出口或雲環境;如果同時看本機時區,就多了一層本地環境判斷。

第三步:匹配域名清單和關鍵字

除了時區,公開分析還提到兩類 hostname 匹配:

  • 是否命中特定域名、域名後綴或服務商清單。
  • 是否包含與 AI 實驗室、模型服務或相關公司有關的關鍵字。

這類匹配的目標,可能不是簡單判斷「是不是中國使用者」,而是把不同風險類型分層:普通自訂閘道、地區相關 endpoint、AI 實驗室相關 endpoint、二者同時命中。

這也是後續編碼需要多個狀態的原因。如果只想表達「命中/未命中」,一個二值標記就夠了;但公開分析裡提到的 Unicode 字元替換,能表示更多組合狀態。

關鍵手法:把結果寫進 system prompt

爭議最大的地方,是檢測結果並不是作為顯式欄位上傳,而是被編碼進 system prompt 中的日期句子。

Claude Code 請求裡本來就會包含類似這樣的系統資訊:

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

公開分析稱,客戶端會在發送請求前對這句話做兩類修改。

第一類是日期分隔符。如果檢測到特定時區,日期可能從:

1
2026-06-30

變成:

1
2026/06/30

第二類是替換 Today's 裡的 apostrophe。肉眼看起來都是一個撇號,但 Unicode 碼點不同。例如:

1
2
3
4
'
ʼ
ʹ

它們在顯示層面非常接近,但在程式眼裡是完全不同的字元。服務端只要檢查 Todays date is 中間那個字元的碼點,就能讀出客戶端編碼進去的狀態。

更直觀地看,可以把它理解成一張「暗號表」:

顯示效果 Unicode 大致含義
' U+0027 普通 ASCII apostrophe,未命中特定條件
U+2019 命中某類域名或後綴清單
ʼ U+02BC 命中 AI 實驗室相關關鍵字
ʹ U+02B9 多個條件同時命中

上面這張表不是為了教人繞過檢測,而是說明它為什麼不容易被肉眼發現:四個字元都像撇號,但碼點完全不同。日誌、終端、網頁字體一渲染,差異會更不明顯。

這就是為什麼很多人把它稱為 prompt steganography,也就是把資訊藏進看起來正常的提示詞文字裡。

為什麼這種標記比較隱蔽

這種做法隱蔽,主要有三個原因。

第一,它不需要額外請求。網路抓包時,你不會看到一個單獨的「上報環境資訊」介面,請求仍然是正常的模型呼叫。

第二,它不需要明顯欄位。即使你檢查 JSON,也可能只看到普通 system prompt。除非逐字元比較 Unicode 碼點,否則很容易忽略撇號差異。

第三,它利用了使用者對系統提示詞的低關注度。開發者通常會關注自己輸入的 prompt、工具呼叫參數、token 消耗和模型輸出,很少逐字檢查客戶端自動拼進去的系統上下文。

從工程角度看,這種機制很聰明;從信任角度看,它也很敏感。因為使用者授權 Claude Code 運行在本地終端裡,是為了讓它讀寫程式碼、執行命令、協助開發,而不是為了讓本地環境特徵被隱藏編碼進請求。

服務端如何讀取這類資訊

如果公開分析屬實,服務端讀取這類標記並不複雜。

它只需要在接收請求後檢查兩件事:

  1. 日期使用 - 還是 /
  2. Today's 中間的 apostrophe 是哪個 Unicode 字元。

換成更接地氣的說法:客戶端發出去的是一張「正常表格」,但日期格式和撇號字體裡夾了兩個小紙條。

例如正常請求可能長這樣:

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

命中特定時區後,可能變成:

1
Today's date is 2026/06/30.

命中某類 endpoint 後,撇號可能變成另一個 Unicode 字元。肉眼看到的仍然像:

1
Today’s date is 2026-06-30.

但程式讀到的不是同一個 '。服務端不需要理解整句話,只要取出兩個位置的字元,就能還原標記狀態。

然後就能把請求歸類為不同狀態。例如:

  • 沒有命中特定清單。
  • 命中某類域名清單。
  • 命中 AI 實驗室相關關鍵字。
  • 同時命中多個條件。
  • 系統時區屬於特定地區。

也就是說,真正的「上報欄位」並不叫 regionproxyrisk_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 編程工具的能力越強,信任問題就越不能只靠「預設相信」解決。

记录并分享
使用 Hugo 建立
主題 StackJimmy 設計