If you use Claude Code with a custom ANTHROPIC_BASE_URL – a corporate proxy, LiteLLM gateway, or any router that isn’t api.anthropic.com – your requests are being silently tagged with invisible Unicode markers. This hit Hacker News in late June 2026 and it’s the kind of thing that matters even if you’ll never write a distillation attack: it changes what gets sent in your system prompt without asking.
Short version:A researcher inspecting the Claude Code binary found that version 2.1.196 embeds hidden markers into the system prompt derived from your API base URL and timezone. You can verify this yourself in about 5 minutes. This walkthrough shows you how, compares two methods, and covers the edge cases that the news posts missed – including some that hit normal developers harder than actual bad actors.
The 60-second background
Prompt steganography hides data in plain sight. The visible sentence reads like a normal date string. You and the model see something boring. The raw bytes on the wire carry a classification signal.
In Claude Code’s case, a function in the binary rewrites the current-date string inserted into the system prompt – the one that reads “Today’s date is 2026-06-30”. The characters look identical in any monospaced font. On the wire, they carry a classification bit derived from two inputs: your ANTHROPIC_BASE_URL setting and your timezone.
Detection is the point. A custom URL pointing at a known reseller domain is a useful signal; a hostname containing “deepseek” or “zhipu” is another. The business case for that is real – unauthorized API resellers and model distillation pipelines are genuine ToS problems. What set off the community was the delivery: obfuscated domain lists (base64-encoded, XOR-decoded with key 91, per the thereallo.dev analysis) plus invisible Unicode, with no mention of any of it in the release notes.
Method A vs Method B: how to actually verify this
Two ways to confirm the behavior. One works in 5 minutes. The other requires fighting TLS.
| Method | What you do | Difficulty | Catches everything? |
|---|---|---|---|
| A. Static grep | Search the installed JS bundle for the marker functions | Beginner | Yes – the logic is right there in the source |
| B. Proxy interception | Route Claude Code through mitmproxy and diff the raw request bytes | Intermediate | Yes, but you have to defeat TLS pinning first |
Method A is the pick here. The functions are named and findable with grep, no proxy setup needed, and you can decode the domain list in a Node REPL in about 30 seconds. Method B is more satisfying if you want to see actual bytes on the wire – but most of the setup time goes toward TLS pinning, not toward learning anything about the steganography itself. Beginner tutorial picks A.
Walkthrough: verifying the markers with grep
You’ll need Node.js and a working Claude Code install. Tested against version 2.1.196 – the version the original researcher analyzed. Newer builds may rename the functions; treat exact names as “as of late June 2026, likely to change.”
Step 1 – Find the binary
Claude Code ships as a Node package. On most systems the bundle sits under your global npm prefix:
npm root -g
# then look inside
ls $(npm root -g)/@anthropic-ai/claude-code/
Step 2 – Grep for the marker functions
Turns out the classifier is split across three short functions – Crt, Rrt, and Qup in the minified bundle (as of 2.1.196). Search the CLI bundle:
grep -oE 'function (Crt|Rrt|Qup)([^)]*)' cli.js | head
If your version still uses those identifiers, you’ll see the three matches. The core check, reconstructed from the thereallo.dev analysis:
function Rrt(baseUrl) {
try {
let host = new URL(baseUrl).host;
return ["api.anthropic.com"].includes(host);
} catch { return false; }
}
If your host isn’t exactly api.anthropic.com, you’re in the “needs classification” branch. That’s it – one hostname check, binary outcome.
Step 3 – Decode the domain list
The domain and keyword lists are base64-encoded, XOR-decoded with key 91 (per the thereallo.dev analysis). Find the encoded blob near those functions and decode it in Node:
const enc = "PASTE_BASE64_HERE";
const out = [...Buffer.from(enc, "base64")]
.map(b => String.fromCharCode(b ^ 91))
.join("")
.split(",");
console.log(out);
That’s the list of hostname fragments the classifier looks for. “deepseek” and “zhipu” are among them, according to the thereallo.dev research.
Quick check: to see invisible characters without any of this, pipe the system prompt through
python3 -c 'import sys; [print(hex(ord(c))) for c in sys.stdin.read()]'. Zero-width Unicode characters (the kind invisible in monospaced fonts) will show up as hex values outside the normal ASCII range. Regular date strings have none.
Here’s what’s odd about using invisible Unicode specifically: it’s harder to accidentally strip than, say, a custom HTTP header, and it survives copy-paste into a text editor. It’s a durable signal – which is probably the point, but it’s also why the community reaction was sharper than usual. A header feels like infrastructure. Invisible characters in a sentence feel different.
What this actually means for your setup
Default endpoint (api.anthropic.com), no custom URL? The Crt() function short-circuits. No marker added. That’s the majority of Claude Code users and nothing here changes their experience.
Gateways are a different story. LiteLLM, corporate proxy, caching layer, OpenRouter-style relay – all of these fail the hostname check. The classification bit rides with every request. And the marker doesn’t distinguish between “shady reseller” and “internal dev tool”: your hostname either matches api.anthropic.com exactly or it doesn’t. That’s a real problem for the edge cases below.
Is that a security problem? Not directly. Is it something you were told about in the release notes? Also no. That gap is the whole story.
Edge cases the news posts skipped
- Timezone triggers too. The base URL isn’t the only input – timezone is also part of the marker system, per the thereallo.dev research. Traveling with your laptop can change what gets embedded even when your gateway config hasn’t moved.
- Self-hosted LLM gateways get the same tag as bad actors. Your internal LiteLLM instance and a shady reseller both fail the
api.anthropic.comcheck. The classifier can’t tell them apart from hostname alone. - The bypass is trivial for actual adversaries. Change hostname, change timezone, patch the binary, wrap the process. As the thereallo.dev post put it: “Any serious adversary can make this signal useless. So the feature mostly punishes… normal developers doing weird but legitimate things.”
- This wouldn’t have been findable a year ago. On March 31, 2026, Anthropic accidentally published the full source code of Claude Code inside an npm package update – roughly 512,000 lines of TypeScript across about 1,900 files, exposed via a debugging source map file included in version 2.1.88. The steganography was probably shipping for months before that; the leak just made function names like
Crt/Rrt/Quptrivially greppable. Without it, you’d be reverse-engineering a minified bundle by hand.
What to do about it
Three options:
- Do nothing. Solo dev on the default endpoint – this never touches your requests.
- Strip zero-width characters before sending. If you build a wrapper around Claude Code, a regex removing zero-width Unicode characters from the outbound system prompt will kill the marker. Check the HN discussion thread for implementation notes – and check the ToS before shipping, because this may constitute modifying Claude Code’s behavior in ways Anthropic prohibits.
- Use the Anthropic API directly. The steganography is in the CLI, not the model. Hit
api.anthropic.comwith your own client and no invisible characters get added. You lose the agent tooling, obviously.
FAQ
Does this affect my code or my model responses?
No. The markers are metadata for Anthropic’s classifiers, not instructions for Claude. Output is identical.
Is Anthropic doing something illegal or against their own policy?
Not that anyone’s shown. Their terms allow detecting abuse and enforcing API usage rules. The community complaint is about disclosure specifically – Claude Code reads your filesystem and runs shell commands, so it’s held to a higher “tell me what you’re doing” standard than a chat app. Silently rewriting the system prompt with invisible characters doesn’t obviously clear that bar. There’s a reasonable argument on both sides; the legality question is the less interesting one.
Will Anthropic patch this out now that it’s public?
Probably they change the encoding, not the intent. The business reason for detecting resellers hasn’t gone anywhere. Expect function names to be renamed and the domain list to shuffle in upcoming releases. Re-run the grep after each Claude Code update if you care about tracking this – the underlying behavior is more likely to get quieter than to disappear.
Next step: open your Claude Code install, run the grep from Step 2, and see whether your version still has Crt/Rrt/Qup or something new. Post what you find – this is the kind of thing that only stays honest if people keep checking.