<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Privacy Security on KnightLi Blog</title>
        <link>https://knightli.com/en/tags/privacy-security/</link>
        <description>Recent content in Privacy Security on KnightLi Blog</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en</language>
        <lastBuildDate>Wed, 01 Jul 2026 21:15:26 +0800</lastBuildDate><atom:link href="https://knightli.com/en/tags/privacy-security/index.xml" rel="self" type="application/rss+xml" /><item>
        <title>Claude Code reportedly hides a signal in requests: one apostrophe can mark your environment</title>
        <link>https://knightli.com/en/2026/07/01/claude-code-prompt-steganography-detection/</link>
        <pubDate>Wed, 01 Jul 2026 21:15:26 +0800</pubDate>
        
        <guid>https://knightli.com/en/2026/07/01/claude-code-prompt-steganography-detection/</guid>
        <description>&lt;p&gt;Claude Code has recently been at the center of a controversy: according to reverse-engineering reports, some versions of Claude Code may read local environment information under certain conditions and encode the detection result into the system prompt sent to the server. The mechanism is not an obvious extra telemetry field. Instead, it modifies an ordinary-looking date line such as &lt;code&gt;Today&#39;s date is 2026-06-30.&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The most interesting part of the public discussion is not the account ban itself, but the detection flow. It shows how a local AI coding tool can carry client environment signals inside a normal request without adding an extra network call. For developers, that is more important than the account-risk angle alone.&lt;/p&gt;
&lt;p&gt;This article summarizes technical details that repeatedly appear in public reverse-engineering posts and community discussion. Because this kind of analysis depends on concrete versions and reverse-engineering results, the following should be read as &amp;ldquo;the implementation logic reported publicly,&amp;rdquo; not as an official complete explanation.&lt;/p&gt;
&lt;h2 id=&#34;first-the-flow-in-plain-language&#34;&gt;First, the flow in plain language
&lt;/h2&gt;&lt;p&gt;In one sentence: it first checks whether you use a custom API address, then checks whether that address looks like a listed service, then checks your computer timezone, and finally hides the result in a date prompt.&lt;/p&gt;
&lt;p&gt;According to public analysis, the rough chain is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Before Claude Code starts or sends a request, it reads &lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the variable does not exist, or still points to the official default address, the special marking logic may not trigger.&lt;/li&gt;
&lt;li&gt;If it points to a custom endpoint, the client parses the URL and extracts the hostname.&lt;/li&gt;
&lt;li&gt;The client compares the hostname with built-in domain lists and keyword rules.&lt;/li&gt;
&lt;li&gt;It also reads the local timezone and checks whether it is a target timezone such as &lt;code&gt;Asia/Shanghai&lt;/code&gt; or &lt;code&gt;Asia/Urumqi&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The client does not add a separate field. It modifies the date sentence in the system prompt.&lt;/li&gt;
&lt;li&gt;The server receives the request and reads the client-side environment mark from the date separator and the apostrophe code point.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The confusing part is step 6. It does not write an obvious field such as:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;region&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;CN&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;proxyMatched&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Instead, it puts the information into ordinary text:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Today&amp;#39;s date is 2026-06-30.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Most people see only a date. The actual signal lives in whether the date uses &lt;code&gt;/&lt;/code&gt; or &lt;code&gt;-&lt;/code&gt;, and in the nearly identical apostrophe inside &lt;code&gt;Today&#39;s&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In pseudocode, the logic looks roughly like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt; 1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 4
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 5
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 6
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 7
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 8
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt; 9
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;10
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;11
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;12
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;13
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;14
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;baseUrl = readEnv(&amp;#34;ANTHROPIC_BASE_URL&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;if baseUrl is empty or baseUrl is official api.anthropic.com:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    send normal system prompt
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;else:
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    hostname = parseHostname(baseUrl)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    domainHit = matchDomainList(hostname)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    labHit = matchAiLabKeywords(hostname)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    timezoneHit = localTimezone in [&amp;#34;Asia/Shanghai&amp;#34;, &amp;#34;Asia/Urumqi&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    dateText = buildDateText()
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    dateText = encodeTimezone(dateText, timezoneHit)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    dateText = encodeDomainStatus(dateText, domainHit, labHit)
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    send system prompt with dateText
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The point is not the exact function names, but the order: read local environment, make a local decision, then write the decision back into prompt text. The user sees an ordinary model request; the server sees a request carrying state.&lt;/p&gt;
&lt;h2 id=&#34;what-each-step-detects&#34;&gt;What each step detects
&lt;/h2&gt;&lt;p&gt;Broken down further, each step does not collect something especially complex, but together they form a fairly clear environment profile.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Step&lt;/th&gt;
          &lt;th&gt;What the client reads&lt;/th&gt;
          &lt;th&gt;What it determines&lt;/th&gt;
          &lt;th&gt;Where the result is written&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;Read environment variable&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Whether a custom API endpoint is used&lt;/td&gt;
          &lt;td&gt;Decides whether later logic runs&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Parse URL&lt;/td&gt;
          &lt;td&gt;hostname&lt;/td&gt;
          &lt;td&gt;Which type of domain the endpoint belongs to&lt;/td&gt;
          &lt;td&gt;Affects the apostrophe code point&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Match lists&lt;/td&gt;
          &lt;td&gt;Domain suffixes and keywords&lt;/td&gt;
          &lt;td&gt;Whether it hits specific services or AI lab keywords&lt;/td&gt;
          &lt;td&gt;Affects the apostrophe code point&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Read system timezone&lt;/td&gt;
          &lt;td&gt;Local timezone&lt;/td&gt;
          &lt;td&gt;Whether it belongs to a target region&lt;/td&gt;
          &lt;td&gt;Affects the date separator&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Rewrite prompt&lt;/td&gt;
          &lt;td&gt;Date sentence&lt;/td&gt;
          &lt;td&gt;Encodes previous decisions&lt;/td&gt;
          &lt;td&gt;system prompt&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So it does not rely on a single signal. &lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt; is only the entry point. The hostname and timezone provide the later classification, and the system prompt carries the final result.&lt;/p&gt;
&lt;h2 id=&#34;trigger-point-anthropic_base_url&#34;&gt;Trigger point: &lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt;
&lt;/h2&gt;&lt;p&gt;The first step mentioned in public analysis is reading the environment variable &lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This variable is commonly used to make Claude Code call a custom API endpoint, enterprise gateway, proxy service, or Anthropic-compatible relay instead of directly requesting the official &lt;code&gt;api.anthropic.com&lt;/code&gt;. In other words, the variable itself is not malicious. Many teams use custom gateways for networking, compliance, auditing, unified authentication, or cost management.&lt;/p&gt;
&lt;p&gt;According to public reverse-engineering analysis, the related logic does not trigger unconditionally for every request. It enters the later checks only after detecting a non-default API base URL. That design matters: it first separates ordinary official-direct users from users with custom endpoints, then performs more detailed environment identification on the latter group.&lt;/p&gt;
&lt;p&gt;At a high level, the process can be divided into three layers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Whether &lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt; is set.&lt;/li&gt;
&lt;li&gt;Whether the URL&amp;rsquo;s hostname hits a specific domain or keyword list.&lt;/li&gt;
&lt;li&gt;Whether the local timezone belongs to a specific region.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is why the controversy focuses on local developer tools such as Claude Code. It runs on the user&amp;rsquo;s machine and can naturally read local environment variables, timezone, config files, and shell context.&lt;/p&gt;
&lt;h2 id=&#34;step-one-extract-the-hostname-from-the-custom-endpoint&#34;&gt;Step one: extract the hostname from the custom endpoint
&lt;/h2&gt;&lt;p&gt;If &lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt; exists, the client can parse it as a URL and extract the hostname.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;https://example-gateway.com/v1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The part that usually participates in matching is not the full URL, but:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;example-gateway.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This has two effects.&lt;/p&gt;
&lt;p&gt;First, the path, query string, and API version do not matter. Only the domain body matters. Second, the matching logic can reuse one domain or keyword list without caring how each provider designs its API path.&lt;/p&gt;
&lt;p&gt;Public discussion says this list is not stored as plain text in the code, but is obfuscated or encoded and decoded at runtime before matching. This does not necessarily prove malicious intent, but it does make the rules harder for ordinary users to discover by string search.&lt;/p&gt;
&lt;h2 id=&#34;step-two-check-the-system-timezone&#34;&gt;Step two: check the system timezone
&lt;/h2&gt;&lt;p&gt;Another repeatedly mentioned signal is the system timezone.&lt;/p&gt;
&lt;p&gt;Public analysis says the logic pays attention to timezones such as:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Asia/Shanghai
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Asia/Urumqi
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Timezone is a subtle signal. It does not require a network request or an IP address, yet it can roughly reflect a user&amp;rsquo;s region or long-term system habits. Many developers may switch network routes, but they usually do not change the computer timezone, because doing so affects calendars, logs, build timestamps, and daily use.&lt;/p&gt;
&lt;p&gt;If the client only looked at IP, a user might be behind a proxy, enterprise exit, or cloud environment. Looking at local timezone adds another local-environment layer.&lt;/p&gt;
&lt;h2 id=&#34;step-three-match-domain-lists-and-keywords&#34;&gt;Step three: match domain lists and keywords
&lt;/h2&gt;&lt;p&gt;In addition to timezone, public analysis also mentions two types of hostname matching:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Whether it hits specific domains, suffixes, or service-provider lists.&lt;/li&gt;
&lt;li&gt;Whether it contains keywords related to AI labs, model services, or related companies.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal may not simply be deciding &amp;ldquo;is this a Chinese user.&amp;rdquo; It may classify different risk types: ordinary custom gateway, region-related endpoint, AI-lab-related endpoint, or multiple hits at once.&lt;/p&gt;
&lt;p&gt;That also explains why the later encoding needs multiple states. If the only goal were &amp;ldquo;hit/not hit,&amp;rdquo; one binary mark would be enough. The Unicode character replacement described publicly can represent more combinations.&lt;/p&gt;
&lt;h2 id=&#34;key-technique-write-the-result-into-the-system-prompt&#34;&gt;Key technique: write the result into the system prompt
&lt;/h2&gt;&lt;p&gt;The most controversial part is that the detection result is not uploaded as an explicit field, but encoded into a date sentence in the system prompt.&lt;/p&gt;
&lt;p&gt;Claude Code requests already contain system information like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Today&amp;#39;s date is 2026-06-30.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Public analysis says the client modifies this sentence in two ways before sending the request.&lt;/p&gt;
&lt;p&gt;The first is the date separator. If a target timezone is detected, the date may change from:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2026-06-30
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;to:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2026/06/30
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The second is replacing the apostrophe in &lt;code&gt;Today&#39;s&lt;/code&gt;. To the eye they are all apostrophes, but the Unicode code points differ. For example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;2
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;3
&lt;/span&gt;&lt;span class=&#34;lnt&#34;&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;’
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ʼ
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ʹ
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;They look very similar when rendered, but they are entirely different characters to a program. The server only needs to check the code point between &lt;code&gt;Today&lt;/code&gt; and &lt;code&gt;s date is&lt;/code&gt; to recover the client-side state.&lt;/p&gt;
&lt;p&gt;More intuitively, you can think of it as a &amp;ldquo;code table&amp;rdquo;:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Display&lt;/th&gt;
          &lt;th&gt;Unicode&lt;/th&gt;
          &lt;th&gt;Approximate meaning&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;&#39;&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;U+0027&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Normal ASCII apostrophe, no specific condition hit&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;’&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;U+2019&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Hit a certain domain or suffix list&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;ʼ&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;U+02BC&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Hit AI-lab-related keywords&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;ʹ&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;U+02B9&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Multiple conditions hit at once&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This table is not for bypassing detection. It explains why the mark is hard to spot by eye: all four characters look like apostrophes, but their code points are different. Logs, terminals, and web fonts can make the difference even less obvious.&lt;/p&gt;
&lt;p&gt;This is why many people call it prompt steganography: hiding information inside text that appears normal.&lt;/p&gt;
&lt;h2 id=&#34;why-this-kind-of-mark-is-hidden&#34;&gt;Why this kind of mark is hidden
&lt;/h2&gt;&lt;p&gt;This approach is hidden for three main reasons.&lt;/p&gt;
&lt;p&gt;First, it does not require an extra request. In a packet capture, you do not see a separate &amp;ldquo;upload environment information&amp;rdquo; endpoint. The request is still a normal model call.&lt;/p&gt;
&lt;p&gt;Second, it does not require an obvious field. Even if you inspect the JSON, you may only see an ordinary system prompt. Unless you compare Unicode code points character by character, the apostrophe difference is easy to miss.&lt;/p&gt;
&lt;p&gt;Third, it takes advantage of how little attention users pay to system prompts. Developers usually inspect their own prompt, tool-call parameters, token usage, and model output. They rarely check the client-generated system context character by character.&lt;/p&gt;
&lt;p&gt;From an engineering perspective, this mechanism is clever. From a trust perspective, it is sensitive. Users grant Claude Code permission to run inside a local terminal so it can read and write code, execute commands, and assist development, not so local environment traits can be invisibly encoded into requests.&lt;/p&gt;
&lt;h2 id=&#34;how-the-server-can-read-this-information&#34;&gt;How the server can read this information
&lt;/h2&gt;&lt;p&gt;If the public analysis is correct, the server-side reading logic is simple.&lt;/p&gt;
&lt;p&gt;It only needs to check two things after receiving the request:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Whether the date uses &lt;code&gt;-&lt;/code&gt; or &lt;code&gt;/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Which Unicode character is used as the apostrophe in &lt;code&gt;Today&#39;s&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In plain language: the client sends a &amp;ldquo;normal form,&amp;rdquo; but two tiny notes are tucked into the date format and apostrophe glyph.&lt;/p&gt;
&lt;p&gt;For example, a normal request might look like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Today&amp;#39;s date is 2026-06-30.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;After a target timezone is hit, it may become:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Today&amp;#39;s date is 2026/06/30.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;After a certain endpoint is hit, the apostrophe may become another Unicode character. To the eye it may still look like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;div class=&#34;chroma&#34;&gt;
&lt;table class=&#34;lntable&#34;&gt;&lt;tr&gt;&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code&gt;&lt;span class=&#34;lnt&#34;&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class=&#34;lntd&#34;&gt;
&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Today’s date is 2026-06-30.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;But the program does not read the same &lt;code&gt;&#39;&lt;/code&gt;. The server does not need to understand the whole sentence. It only needs to extract characters at two positions to restore the mark state.&lt;/p&gt;
&lt;p&gt;It can then classify the request into states such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;no specific list hit&lt;/li&gt;
&lt;li&gt;hit a certain domain list&lt;/li&gt;
&lt;li&gt;hit AI-lab-related keywords&lt;/li&gt;
&lt;li&gt;hit multiple conditions at once&lt;/li&gt;
&lt;li&gt;system timezone belongs to a specific region&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In other words, the real &amp;ldquo;reporting field&amp;rdquo; is not named &lt;code&gt;region&lt;/code&gt;, &lt;code&gt;proxy&lt;/code&gt;, or &lt;code&gt;risk_flag&lt;/code&gt;. It is hidden in character differences inside natural-language system prompt text.&lt;/p&gt;
&lt;h2 id=&#34;how-this-differs-from-ordinary-risk-control&#34;&gt;How this differs from ordinary risk control
&lt;/h2&gt;&lt;p&gt;It is not strange for a service provider to run risk control. API abuse, account resale, model distillation, abnormal concurrency, and regional policy violations are real problems. The issue is transparency and boundaries.&lt;/p&gt;
&lt;p&gt;Ordinary risk control is easier to understand: login IP, payment region, request frequency, device information, organization account, API key usage pattern. These signals are also sensitive, but users can roughly expect platforms to use them for security decisions.&lt;/p&gt;
&lt;p&gt;The controversy here is different: if the client really encodes local timezone and custom endpoint match results into the system prompt, users are unlikely to learn it from the product UI, request fields, or release notes. For a developer tool with filesystem and shell permissions, that directly affects trust.&lt;/p&gt;
&lt;h2 id=&#34;what-developers-should-pay-attention-to&#34;&gt;What developers should pay attention to
&lt;/h2&gt;&lt;p&gt;The lesson is not simply &amp;ldquo;do not use a certain tool.&amp;rdquo; It is to re-examine the trust boundary of local agents.&lt;/p&gt;
&lt;p&gt;Developers can watch for several things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;whether the local CLI reads environment variables, config files, timezone, system language, and similar environment data&lt;/li&gt;
&lt;li&gt;whether the automatically assembled system prompt can be viewed, exported, and audited&lt;/li&gt;
&lt;li&gt;whether the final payload can be inspected before the request is sent&lt;/li&gt;
&lt;li&gt;whether release notes disclose new detection, risk-control, or telemetry logic&lt;/li&gt;
&lt;li&gt;whether enterprise gateways can log and audit upstream request content&lt;/li&gt;
&lt;li&gt;whether the local tool provides switches to disable telemetry or sensitive environment reads&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If an AI coding tool can read and write project files, execute shell commands, access git, and read environment variables, it is essentially a high-privilege local agent. The transparency bar for such tools should be higher than for a normal chat webpage.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;The real discussion in this Claude Code request-watermark controversy is not whether one account was banned, but how a client may encode environment information into a normal request.&lt;/p&gt;
&lt;p&gt;According to public reverse-engineering analysis, the flow is roughly: read &lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt;, parse the hostname, match domain and keyword lists, check the system timezone, then write the result into the system prompt through the date separator and Unicode apostrophe. The whole process needs no extra network request and no new explicit field.&lt;/p&gt;
&lt;p&gt;If this is part of anti-abuse risk control, the provider should still explain the boundaries more transparently. The more permission developers give a local agent, the more they need to know what it reads, what it rewrites, and what it sends.&lt;/p&gt;
&lt;p&gt;As AI coding tools become more capable, trust cannot be solved by &amp;ldquo;just assume it is fine.&amp;rdquo;&lt;/p&gt;
</description>
        </item>
        
    </channel>
</rss>
