Check S17 · agent surface family · severity HIGH · automatic · one of the 17 skill and plugin checks
Maps to OWASP LLM01 OWASP LLM03 ATLAS AML.T0074 ATLAS AML.T0068 Letters from another script that imitate Latin ones: a name built to pass for another (masquerading), or a word an agent reads that a filter looking for the Latin spelling does not (obfuscation).
The question this check asks: does a word in this skill mix Latin letters with Cyrillic or Greek letters that look like them? The Cyrillic с and the Latin c are different characters that print the same. Spelled with one of them, сurl reads as curl to a person and is another word to anything that compares text.
A name is the first thing a person trusts. A skill named with one imitating letter can sit beside the skill it copies and look identical in a listing, an install line or an agent's answer. The 2026 skill campaigns relied on names built to be mistaken for others: the malicious skills page has the record with its sources, including a skill published under the platform's own name.
Inside the text an agent reads, an imitating letter does a second job: it hides a word from anything that searches for the Latin spelling. An instruction to run сurl is still an instruction to the reader, and an agent can follow it, but a filter looking for curl does not see it.
What counts. A word, meaning a run of letters, that holds at least one Latin letter and at least one Cyrillic or Greek letter, where every Cyrillic or Greek letter in it has a Latin prototype in Unicode's own confusables table (Unicode Technical Standard #39, version 18.0.0, kept with the product as data). A word wholly in one script is never counted, whatever the script, so a skill written in Russian, Greek, Chinese or Japanese writes no row. A letter with no Latin prototype is not this shape: 10μs and π are ordinary text. Accented Latin letters such as é or ï are Latin and never count.
Where it looks. The listing's name and every skill's own name from its frontmatter, and the markdown this tier read: each SKILL.md and the other markdown files beside it.
The grade. In a name it is HIGH at 15 points, one row, skill-mixed-script:name, naming the words. In the markdown it is MEDIUM at 5 points, one row a file, skill-mixed-script: and the file's path, with the count of such words and the first five. At most three files are listed. It is never CRITICAL, so it never makes the verdict BLOCK on its own: a HIGH warns under the default profile and blocks under the strict organisation profile, as any HIGH does there.
Its neighbour in S4. A name spelled to look like a known skill or owner, with or without a Latin letter left in it, is the lookalike check's case. Since 1 November 2026 S4 compares any name that holds a letter outside plain ASCII by its TR39 skeleton, so a name that looks the same as a known one matches it.
Measured on 1 November 2026 through the same code as /v1/skill/score, on the three lists this roster is measured on as they stood that day: 167 skills.sh keys, all 310 official-marketplace plugins and the first 200 community-marketplace plugins — 677 keys. S17 wrote 2 rows on 1 key, both MEDIUM, in the markdown of a community plugin that checks skills for this very shape and spells a vendor's name with a Cyrillic letter to show it. No name carried the shape, S4's new skeleton comparison matched no name, and no verdict changed. Only the files this tier read were looked at: at most twelve a key.
A word that mixes scripts on purpose. A bilingual product name, a transliteration written half in each alphabet, or a mathematical variable beside a unit can be such a word. Each is a row in the markdown, MEDIUM, naming the word, and reading it is enough to tell the cases apart.
A name in two scripts. A skill whose author writes its name partly in Cyrillic for their own audience gets the HIGH. That is the cost of the rule: a name is exactly where a look-alike letter does its harm, and from here a name written that way for an honest reason and one written to pass for another look the same.
A word wholly in another script. A word spelled entirely in Cyrillic letters that look like a Latin word is not counted here, because it cannot be told from a word of that language; in a name it is S4's skeleton comparison, and in the text it is not read. Other look-alikes. Only Cyrillic and Greek letters are read as imitating Latin ones; Armenian, Cherokee and the mathematical letter blocks are not, and neither are ASCII look-alikes such as 1 for l, which S4's edit distance meets. Invisible characters. Those are S8's. Files this tier did not read. Twelve content reads per key: a file that was not fetched has no row, and a partial scan is never a pass on it. Scripts. Only markdown and names are read for this shape; a script's identifiers are not. ClawHub (@owner/slug) is read from the listing's own record and its published package — the package streamed under a 512 KB cap and read for at most twelve files at 64 KB each.
Scope, the same on every page of this roster: RepoGates does not see git clone, package managers or curl — outside Claude Code with the RepoGates plugin, whose hook refuses a clone or install that names a blocked repository on the command line, before it runs. The hook sees Bash tool calls in that one client and nothing else: npx skills add owner/repo and claude plugin install name@marketplace are shell lines it judges; /plugin install is a slash command the hook never sees; a skill copied by hand into ~/.claude/skills is never seen at all. Edit and Write are read for one thing only — an API key about to be saved — checked on the machine and never sent; that guard asks, it never denies. This tier reads the listing record, the tree once, and at most twelve files of 64 KB each — SKILL.md files first, then a plugin's agents and commands, then the scripts beside each skill; more than fits is a partial scan, stated, and never a pass on what was not read. The deep scan reads the whole package. A PASS means nothing matched on what was read, never that the skill was run.
Print every word of a skill that holds a Cyrillic or Greek letter beside a Latin one:
python3 - SKILL.md <<'PY'
import re, sys, unicodedata
for w in set(re.findall(r"[^\W\d_]+", open(sys.argv[1], encoding="utf-8").read())):
s = {unicodedata.name(c, "?").split()[0] for c in w}
if "LATIN" in s and s & {"CYRILLIC", "GREEK"}:
print(w, [unicodedata.name(c) for c in w if not c.isascii()])
PY
It lists more than the check does: the check also asks that every foreign letter in the word imitate a Latin one, which the table of Unicode's confusables decides.
Is a skill written in Russian or Greek a finding? No. A word written wholly in one script is never a finding, whatever the script. The check looks only for a word that holds Latin letters AND Cyrillic or Greek letters that imitate Latin ones, such as curl spelled with a Cyrillic first letter. Localisation is not an attack, and a check that could not tell the two apart would be worse than none.
Why is 10μs not a finding? Because the Greek mu has no Latin prototype in Unicode's confusables table: it does not imitate a Latin letter, so the word is not the look-alike shape. Every Cyrillic or Greek letter in the word must imitate a Latin one for the word to count.
What about a name spelled wholly in Cyrillic to look like a known one? That is the lookalike check's case, S4. It compares each name that holds a letter outside plain ASCII by its Unicode skeleton, so a name whose letters all look like a known skill's or owner's matches it even when no Latin letter is left in it. S17 is for the word that mixes the two.
S4 Lookalike owner or name — a name one edit from a known one, or the same once look-alike letters are read as Latin · S8 Hidden Unicode — characters a reader cannot see at all · S9 Instruction shapes — what the text tells the agent to do.
Add RepoGates to Chrome Pricing
Data on this page: Unicode Technical Standard #39, confusables.txt version 18.0.0 (Unicode License V3), kept with the product; weights from the product's scoring table.