Skip to content

The AI That Corrupted Its Own Payloads: Cyrillic Twins Hidden in Base64

Above ~15 KB of base64 an AI swapped Latin letters for Cyrillic twins. The free guard: strict decode, retry, then gzip — never fight the transport.

Published
Updated
AI-generated, checked by independent automated gates, no human editorial review.
How this guide was made
The AI That Corrupted Its Own Payloads: Cyrillic Twins Hidden in Base64

AI-generated. Written by an automated system, checked by independent automated gates, and published without human editorial review. How we label and verify.

The moment it stopped being a transport problem — our first AI emission corruption case

We were moving 34 translated files into a live system, each one written back byte-for-byte exactly. Some transfers kept failing. Not the small ones. Only the big blocks — the ones past roughly 15,000 characters.

At first we assumed network, sockets, headers — the usual suspects. We changed transports. Nothing helped.

What actually happened, and how we saw it

The turning point was the longest single job we’d run to that date: 34 files translated to English with every technical term preserved, and every output written back exactly as bytes. We verified more than 22 of those updates by reading the result directly from the receiving system rather than trusting the delivery receipt. That extra step paid for itself.

When we inspected the failures we found a strange pattern. When an AI emitted a very long encoded text block — roughly beyond 15,000 characters — some occurrences of Latin A and O were replaced by visually identical Cyrillic letters. A human reading the file saw nothing wrong. Machines did.

This was not an escaping bug. The corruption happened as the text was emitted. The transmitter returned a green success. The damage only revealed itself later, when someone tried to decode what arrived. We documented this on 2026-06-20.

The experiment that proved the cause

We added a strict decoder at the receiving end that rejects any block containing those look-alike characters because they aren’t valid in our chosen encoding. The decoder does nothing fancy — it errors out and asks for the same block again. We watched it happen live: one rejection, then an identical retry which arrived clean and passed immediately.

That one replay convinced us the fault was in emission, not transport. The receiver caught what the sender had silently corrupted.

The second, cheaper fix

Three files were never writable cleanly when sent at full size — their encoded forms were about 38, 54 and 60 kilobytes. We compressed them first, cutting them to roughly a third. After that every file transferred perfectly, including Chinese characters and the Norwegian letters æ, ø and å.

Shrinking the danger zone worked. The write process only faltered on long, uniform runs of encoded text. Make the run shorter and the AI stopped swapping look-alikes into the stream.

Rule that fell out

A green delivery receipt proves nothing — corruption can happen as the sender writes its own text, and only decoding what actually arrived reveals it. If a long encoded block can contain visually identical but invalid characters, make the receiver reject and retry, and avoid the danger zone by shrinking the block size.

One example detail (taste, not the whole cookbook)

We added a simple check that scans for characters outside the allowed encoding range and rejects the block when found. That single check produced one visible rejection and then a clean identical retry — live evidence that the sender had emitted bad bytes and then emitted good ones on the next pass.

The tested, runnable version with full retry logic and integration lives in the members’ library.

A countercase we recorded

Not every large file failed. Several blocks over 15,000 characters transferred without issue. The corruption was intermittent. That meant the problem wasn’t a deterministic threshold alone but a fragile emission state that sometimes flips during long runs. Compression reduced probability to practically zero by preventing those long runs in the first place.

How we know

We recorded the failures and replays, read back the stored outputs instead of trusting success statuses, and confirmed the character substitutions by comparing byte sequences. The date we logged the discovery is 2026-06-20.

Questions we got

Could better transport ever have fixed this?

No. We tried different transports and observed the same corrupted bytes arriving. The bytes were corrupted before delivery; changing the carrier didn’t help.

Why reject-and-retry rather than silently fix?

Because the look-alike characters are invalid in our encoding. A strict decoder rejects them cheaply, forcing a resend. Silent on-the-fly fixes risk masking other corruption and break auditability.

Is compression always enough?

Compression stopped the failures we saw by shortening encoded runs, including for files with multibyte characters like Chinese and Norwegian letters. But because the corruption is intermittent, compression reduces the chance to near zero rather than guaranteeing it for every possible future emission scenario.

We walked into this thinking transport; we left thinking emission. Keep the receiver strict. Keep the blocks small. The machine can look like it’s telling the truth and still be quietly sabotaging itself. That is the shape of AI emission corruption: silent, self-inflicted, and invisible until you decode what actually arrived.

Sources: internal incident documentation and tests logged 2026-06-20.

What the Unicode and Base64 standards say about look-alike characters

The Unicode Consortium has a whole technical standard for this class of problem: UTS #39, Unicode Security Mechanisms. It ships a data file of characters that can be mistaken for one another. The two substitutions we saw are both in it. The file maps CYRILLIC CAPITAL LETTER A → LATIN CAPITAL LETTER A and CYRILLIC CAPITAL LETTER O → LATIN CAPITAL LETTER O. To a reader they are the same shape. To a computer they are different code points.

UTS #39 detects confusable strings by reducing each one to a “skeleton” and comparing the results. The standard is clear that the skeleton is an internal step, not a repaired text: it describes it as an intermediate form similar to a hashcode. The same document defines Mixed-Script Detection, which flags text that combines scripts such as Latin and Cyrillic. The risk it describes is text that looks right and is not.

Base64 is defined in RFC 4648, and its rules are strict by default. The encoding uses a 65-character subset of US-ASCII. A Cyrillic letter is not in that set, however much it resembles one that is. On how a decoder should treat such input, the RFC says implementations MUST reject encoded data containing characters outside the base alphabet, unless the specification referring to this document explicitly states otherwise. Its security section explains why leniency is risky: silently ignoring non-alphabet characters opens a covert channel that can be used to leak information.

Research on language models shows that homoglyphs matter to them in a different way than they do to people. The SilverSpeak paper studies deliberate homoglyph substitution as a way to evade AI-text detectors, and reports that homoglyph-based attacks can effectively circumvent state-of-the-art detectors. That work is about substitutions made on purpose by an attacker. It does not describe a model producing look-alikes by itself during long output, which is what we observed. We cite it for the narrower point that a Cyrillic A and a Latin A are different characters to software, however alike they look.

How the standards line up with what we saw

Our strict decoder is not a clever invention. It is RFC 4648’s default behavior: reject anything outside the alphabet. That is why the guard cost nothing. The look-alike letters were never valid Base64, so a decoder that follows the RFC turns silent damage into a loud error and a resend. We watched exactly that once, live: one rejection, then an identical retry that arrived clean.

The fact that a human reading the file saw nothing wrong is the same visual-spoofing problem UTS #39 was written for, moved from identifiers into a data stream. And the two letters we caught, Cyrillic A and O standing in for Latin A and O, are pairs the Unicode confusables data already lists.

The standards also explain why we chose reject-and-retry over repair. UTS #39’s skeleton is meant for comparison, not for producing corrected text, and the RFC’s warning about covert channels is a warning about decoders that quietly tidy up input. Silent fixes would have hidden the base64 corruption instead of surfacing it.

What the standards do not explain is why long emissions went wrong intermittently and shorter ones did not. That observation is ours. Compressing three files to roughly a third of their size removed the failures we saw, and we still describe that as lowering the odds, not as a guarantee.

A checklist for encoded data written by a model

  1. Decode strictly. Use a decoder that rejects characters outside the Base64 alphabet instead of skipping them.
  2. Read back what the receiver stored and decode it. A success status is not evidence.
  3. On rejection, request the same block again. Do not patch characters in place.
  4. Scan text fields for mixed scripts where you expect only one, using the Unicode confusables data as a reference.
  5. Keep encoded blocks short. Compress before encoding when the payload is large.
  6. Log every rejection and retry so intermittent failures leave a trace.

More questions

Can we use the Unicode confusables list to fix a corrupted block automatically?

We would not. UTS #39 presents its skeleton transform as an intermediate form for comparison, not as text to store. More practically, for Base64 the bytes must be exact. Mapping a look-alike back to the letter you think it replaced is a guess, and a wrong guess still decodes into wrong data. Rejecting and resending gives you the sender’s real output.

Are lenient Base64 decoders wrong?

Not always. RFC 4648 lets a referring specification allow some non-alphabet characters, for example line feeds in encoded data in some formats. Outside those cases, its default is to reject. If your pipeline carries data written by a model, the strict default is the one that catches look-alike characters.


How we know

The factual claims in this article come from our verification store — each with a source type, a confidence label and a reference. The method is documented on How we know.

– Our first instinct — a breaking long transfer needs a better transfer method — turned out to be wrong. No transport change helped, because the sender itself was the fault: the AI corrupted the data as it wrote it out. What worked was making the failure catch itself, retrying until clean, and shrinking the data so the danger zone was never entered at all. | source: documented source | conf: mythbuster | ref: our living-lab log + seksjon EMISSION-CORRUPTION (‘Overførbart’) 2026-06-20
– The discovery came out of the longest single job we had completed at that point: translating 34 files to English with every technical term preserved, and writing each one back byte-for-byte exactly. More than 22 of those updates were verified by reading each result back from the receiving system — not by trusting the delivery receipt. | source: documented source | conf: sourced | ref: our living-lab log + seksjon EMISSION-CORRUPTION (bakgrunn + status LOV-013) 2026-06-20
– The guard costs nothing: the look-alike letters are not valid characters in the encoding, so a strict decoder simply rejects the corrupted block with an error instead of storing broken data. The cure is to send again. We watched it happen live — one rejection, then a clean, identical retry that went straight through. | source: documented source | conf: verified | ref: our living-lab log + seksjon EMISSION-CORRUPTION (‘Vakten’) 2026-06-20
– Three files were too large to write out cleanly at all — their encoded versions ran to 38, 54 and 60 kilobytes. The fix was shrinking, not better transport: compressing the data first cut it to roughly a third, and every file then transferred perfectly, including Chinese characters and the Norwegian letters æ, ø and å. | source: documented source | conf: verified | ref: our living-lab log + seksjon EMISSION-CORRUPTION (‘Gigant-veggen + fiks’) 2026-06-20
– On 2026-06-20 we documented a failure we had never seen named anywhere: when an AI writes out a very long encoded text block — beyond roughly 15,000 characters — it occasionally swaps in look-alike letters from the Cyrillic alphabet in place of Latin A and O. The text looks identical to a human eye. The data is silently corrupted. | source: documented source | conf: verified | ref: our living-lab log + seksjon ‘EMISSION-CORRUPTION oppdaget + vakt + gzip-fiks’ 2026-06-20
– This corruption is not an escaping bug, and no delivery receipt catches it: the damage happens as the AI emits its own text, the corrupted block is delivered ‘successfully’ with a green status, and it fails only later, when someone actually tries to decode what arrived. | source: documented source | conf: observed | ref: our living-lab log + seksjon EMISSION-CORRUPTION (‘ny feilflate — HTTP 200 fanger den ikke’) 2026-06-20

Related guides

Systems & Mental Models · Published

Our ‘Find Everything’ Catalog Couldn’t Find a Tool We Built

We tested our find-everything catalog and a tool we had built was invisible. Why intake-first archives fail, and the loop that fixed ours.

Systems & Mental Models · Published

We Set Out to Build an Autonomous Loop. It Already Existed.

We set out to build an autonomous loop — it was already running. Why read-first norms fail and the structural gate that stopped us rebuilding our own work.

Systems & Mental Models · Published

We Rebuilt the Same Skill Four Times. Here’s What Finally Made It Idiot-Proof for AI

We rebuilt one trivial skill four times because agents kept misreading it. Here are the five ambiguities we found, and the rule that finally made it idiot-proof for AI.