disarm

Bidirectional controls

Detect Trojan Source in source code

Paste code to find the bidirectional control characters that make it render differently from how it compiles. You get a line and column for every one, and the two readings side by side — what a reviewer sees, and what the compiler sees.

The tool

The example is the commenting-out pattern from the 2021 Trojan Source paper: a right-to-left override and two isolates make an if statement appear to sit inside a comment, so the line beneath it looks unreachable and is not.

Paste source code to check it.

Nothing is uploaded. The engine runs inside this page.

What a reviewer sees

Your code as the browser renders it, with the bidi algorithm applied.


        

What the compiler sees

The same bytes in logical order, with each control shown where it sits.

Loading the engine…

A worked example

The tool above needs JavaScript. This is the same finding written out, so it is legible without running anything.

Controls found in the example source
LineColCodepointName
57U+202ERIGHT-TO-LEFT OVERRIDE
511U+2069POP DIRECTIONAL ISOLATE
524U+2066LEFT-TO-RIGHT ISOLATE
526U+2066LEFT-TO-RIGHT ISOLATE
724U+202ERIGHT-TO-LEFT OVERRIDE
728U+2069POP DIRECTIONAL ISOLATE

Six controls across two lines. The printf beneath them appears to be commented out and is not, so a reviewer approves a program that grants administrator access. No character is misspelled and no identifier is confusable; the file is exactly what it appears to be, read in a different order.

The example carries no direction conflict — that is the other attack. varonis.com.ו contains no control character at all, yet reverses under the bidi algorithm because the final letter is a real Hebrew one. Removing controls cannot fix it, which is why the two are reported apart.

The same thing in your own code

Each block is a file CI compiles and runs, so none can quietly stop working, and all seven print the same line. disarm on GitHub →

# Detect Trojan Source, and the direction conflict that stripping cannot fix.
#   pip install disarm
from disarm import strip_bidi, has_bidi_conflict

# The commenting-out line from the Trojan Source paper: an override and two
# isolates, so the `if` appears to sit inside the comment.
TROJAN = "    /*‮ } ⁩if (isAdmin)⁦ ⁦ begin admins only */"
# No control character at all — the final letter is a real Hebrew vav.
CONFLICT = "varonis.com.ו"

clean = strip_bidi(TROJAN)
controls = len(TROJAN) - len(clean)

# The two checks catch different attacks, which is why both are needed.
assert controls == 4, controls
assert not has_bidi_conflict(TROJAN), "overrides are not a direction conflict"
assert has_bidi_conflict(CONFLICT), "real RTL letters are"
assert strip_bidi(CONFLICT) == CONFLICT, "and there is nothing to strip"

print(f"ok: {controls} bidi controls stripped; a conflict detected where there are none")

The characters involved

Bidirectional controls exist for a real purpose: text mixing Arabic or Hebrew with Latin needs them. In source code they have no legitimate use outside string literals and comments meant to contain such text, which is why their presence is worth flagging rather than silently removing.

CodepointAbbrNameEffect
U+202DLROLeft-to-right overrideForces the following text left to right, whatever it contains.
U+202ERLORight-to-left overrideForces the following text right to left. The usual vehicle for the attack.
U+202ALRELeft-to-right embeddingOpens a left-to-right run; ended by PDF.
U+202BRLERight-to-left embeddingOpens a right-to-left run; ended by PDF.
U+202CPDFPop directional formattingEnds the most recent embedding or override.
U+2066LRILeft-to-right isolateOpens an isolated run, ended by PDI.
U+2067RLIRight-to-left isolateOpens an isolated right-to-left run.
U+2068FSIFirst strong isolateDirection taken from the first strong character inside.
U+2069PDIPop directional isolateEnds the most recent isolate.
U+200ELRMLeft-to-right markAn invisible strong character used to nudge ordering.
U+200FRLMRight-to-left markThe right-to-left counterpart.
U+061CALMArabic letter markAs RLM, for Arabic-script context.
U+00ADSHYSoft hyphenInvisible unless a line breaks there.

The Trojan Source paper recommends that build pipelines refuse, or at least warn on, unterminated overrides. Compilers have since added their own checks — rustc and GCC both warn — but they cover their own source, not the data your program reads.