AI & ML // // 9 min read

Anthropic Shipped Spyware in Claude Code, and It Is Hidden in the Apostrophe

A reverse engineer who goes by "the real lo" just published a teardown of Claude Code 2.1.196 that nobody in the AI tooling press is going to want to talk about. The production binary of Anthropic's flagship coding agent contains

Bala Kumar Senior Software Engineer

A reverse engineer who goes by "the real lo" just published a teardown of Claude Code 2.1.196 that nobody in the AI tooling press is going to want to talk about. The production binary of Anthropic's flagship coding agent contains a hidden classification mechanism that fingerprints your routing setup, encodes the result into a single invisible Unicode character, and ships that fingerprint The mechanism is obfuscated with Base64 and an XOR key so that a normal code audit will not find it. It fires on legitimate developer use, and it is trivially bypassable by anyone who actually wants to hide.

I am not going to soften this. The word for software that silently classifies your activity, encodes the result into something you cannot see, and ships it to a remote backend, is spyware. Anthropic shipped spyware in Claude Code. It is signed by Anthropic, it is in the production binary on your disk right now, and it is enabled by default.

Here is exactly what is in the box, in the code, and how I know.

The trigger, in plain terms

The mechanism is gated on a single environment variable: ANTHROPIC_BASE_URL. That is the standard override that lets you route Claude Code at an internal gateway, a local proxy, a model router, a third-party reseller, or a research setup. The moment you set it to anything other than api.anthropic.com, the function Crt() returns false instead of true, and the rest of the classification path executes.

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;
  }
}

Crt() only short-circuits to the safe path when the override is unset or points exactly at api.anthropic.com. Everything else, including typos and unresolvable hosts, drops into the classification path. That is the first thing that should bug you: a misconfigured ANTHROPIC_BASE_URL does not produce a warning, it produces a fingerprint. Anthropic is classifying you without telling you, and it is classifying you hardest when you do something the company has not specifically blessed.

What gets classified, and how

The function Zup() reads the host from your base URL and your system timezone, then runs two checks:

  1. Known domain match. It compares your hostname against a decoded allow-or-deny list. If your hostname equals any entry, or is a subdomain of any entry, the known flag flips to true.
  2. Lab keyword match. It runs String.prototype.includes against a decoded list of substrings: deepseek, moonshot, minimax, xaminim, zhipu, bigmodel, baichuan, stepfun, 01ai, dashscope, volces. If your hostname contains any of those substrings, the labKw flag flips to true.
  3. China timezone match. If your system timezone is Asia/Shanghai or Asia/Urumqi, the cnTZ flag flips to true.

Those three booleans are then encoded into a single Unicode character that replaces the apostrophe in "Today's date." A normal user sees a straight '. A user whose hostname matches a known domain sees a Unicode right single quotation mark (U+2019). A user whose hostname contains a lab keyword sees a Unicode modifier letter apostrophe (U+02BC). A user who trips both checks sees a different modifier letter (U+02B9). The timezone flag, if set, swaps the date separator from - to /.

The full mapping:

ConditionApostrophe in "Today's"Date separator
Normal (no fingerprint)' (U+0027)-
Known domain only' (U+2019)-
Lab keyword only' (U+02BC)-
Known domain + lab keyword' (U+02B9)-
China timezone only(no marker)/
Multiple flags can compose--

Every entry in that table is visually indistinguishable from a normal apostrophe in most monospaced fonts at normal zoom. That is the point. The model, the backend, and any well-instrumented log pipeline can read the marker. The human in front of the terminal cannot. This is not a stretch of the word "spyware." This is what the word was coined for.

The obfuscated list, decoded

The domain and keyword lists are not stored as strings. They are stored as Base64 blobs XORed with the integer 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(",");
}

The decoded lab keyword list is short and direct: deepseek,moonshot,minimax,xaminim,zhipu,bigmodel,baichuan,stepfun,01ai,dashscope,volces. The decoded domain list is much larger, and reading it tells you exactly who Anthropic considers a competitor or a leakage path. Some entries from the list:

CategoryExamples (decoded)
Chinese corporatesbaidu.com, alibaba-inc.com, alipay.com, antgroup-inc.cn, bytedance.net, kuaishou.com, xiaohongshu.com, jd.com, bilibili.co, iflytek.com
Chinese AI labsstepfun-inc.com, moonshot.ai
Proxy / gateway / reselleranyrouter.top, claude-code-hub.app, claude-opus.top, openclaude.me, proxyai.com, yunwu.ai, zenmux.ai

The full list is published at https://cdn.thereallo.dev/blog/assets/cc-domains.js. There are hundreds of entries, and the proxy category is the longest. The list is a snapshot of "who is currently routing Claude Code traffic somewhere Anthropic does not want it routed." Anthropic has built a surveillance list of its own customers' infrastructure and shipped the decoder in the production binary.

Why this is the wrong way to do it

I want to be fair to Anthropic, because the use case is real. Distillation attacks, where a third party uses Claude to generate training data for a competitor model, are a genuine problem. API resellers that violate terms of service are a real revenue leak. Internal gateways and model routers that bypass the official rate limits are a legitimate concern. The classification logic in Zup() is a defensible signal for those problems.

The implementation is the part that makes it spyware, not a security feature. Four things:

  1. The signal is invisible to the user. The list is in the binary at all. Privacy notices cannot defend a mechanism the user cannot see. Anthropic's published privacy disclosures do not mention this mechanism, and the binary does not warn about it.
  2. The signal is trivially bypassable. Any serious adversary can change the hostname, change the timezone, patch the binary, wrap the process in a relay, or strip the ANTHROPIC_BASE_URL env var and re-inject it at the SDK level. The feature has zero effect on the people it is designed to catch, and full effect on the people it is not designed to catch.
  3. The trigger fires on legitimate use. Internal enterprise gateways, model routers, cost-optimization layers, research sandboxes, and developer ergonomics setups all set ANTHROPIC_BASE_URL for non-malicious reasons. The fingerprinting path fires on all of them. Anthropic is spying on its own paying enterprise customers and labelling them as suspicious.
  4. The mechanism is hidden in plain sight. Base64 plus an XOR key is not encryption. It is obfuscation. Any reverse engineer with five spare minutes finds the list. The only audience the obfuscation is effective against is the developer running the tool, the developer Anthropic's terms of service are supposed to protect, and the developer who is the only one being harmed by it.

So the feature mostly punishes the exact people who are easier to fingerprint: normal developers doing weird but legitimate things. Adversaries walk through it. This is the defining property of bad surveillance. It is harmless to the people it is aimed at and invasive to the people it is not.

What Anthropic should have done

There is a boring, correct way to do everything in Zup(). Send an explicit telemetry field, document it in release notes, add it to the privacy notice, give users a flag to disable it, and let enterprise admins turn it off via policy. The classifier can run server-side on a documented signal. The user can see what is being sent and to whom. The terms of service can be enforced with a clean revocation path. None of that requires Unicode steganography, and none of it punishes legitimate users.

Anthropic did not do any of that. backend, with no notice, no opt-out, and no documentation. The mechanism is not a bug. It is a feature, in the production binary, signed by Anthropic's own Apple developer team identifier Q6L2SF6YDW.

The trust calculus just shifted

Coding agents live on the wrong side of a scary boundary. They can read your repo, summarize secrets by accident, run shell commands, install packages, edit files, and push commits to remotes. The productivity gain is worth the risk for most developers, but only because we have decided to trust the client binary. The moment that binary starts hiding classification bits inside invisible prompt punctuation, the trust calculus shifts. Every other privacy claim becomes harder to believe, because the boring parts of the tool are no longer boring.

The fix is not "remove the classification." The fix is "make the classification visible." A documented telemetry field beats a Unicode steganography trick in every dimension that matters: it is auditable, contestable, and bypassable in the same way for the developer and the adversary. That is what trustworthy developer tools look like, and it is what Anthropic has decided not to ship.

If you are running Claude Code with a custom ANTHROPIC_BASE_URL today, the practical impact is small. Your date string probably still looks normal, and your requests are still working. But the binary on your disk is making a decision about you that it does not tell you about, and the decision is being sent to a backend that the privacy notice does not mention. The Anthropic team is going to have to answer for that, and "it was on the list for compliance reasons" is not going to be a sufficient answer.

Source: https://thereallo.dev/blog/claude-code-prompt-steganography (research by "the real lo", Claude Code 2.1.196, SHA256 6fc6e61ab7582c2bf241225ff90d9f79e91d69380cb9589fc9dedd3a30070f5a, signed by Anthropic on 2026-06-29)