Unicode-LE
io.github.nolindnaidoov1.0.0更新于 Oct 3, 2026
Find the Unicode that hides meaning: bidi controls, invisibles, homoglyphs and mixed scripts.
概览
让助手扫描文本或代码中隐藏的 Unicode 风险,例如双向控制符、不可见字符、同形异义字符和混合文字。
- 功能
- 提供一个工具 detect_unicode_risks,接收内容并返回双向控制符、易混淆字符、混合文字单词、不可见字符、未分配或私用码位、非 NFC 行以及异常空白字符的发现结果。每条结果包含类型、严重程度、行列号、字节偏移、码位和文字系统,对 JSON、YAML、TOML、INI、properties、env、CSV 和 TSV 还会给出键路径。结果默认上限为 500 条并带截断标记,拒绝处理的情况也会报告,因此空结果不会被误认为文档干净。
- 适用场景
- 适合在审查拉取请求中的 Trojan Source 问题、筛查名称或标识符中的伪造字符,或解释因零宽空格或分解字符而始终无法匹配的字符串时使用。它面向代码和文本审查,而非通用文件管理。
- 运行要求
- 通过 npx unicode-le-mcp 以 stdio 在本地运行,也可用 npm install -g unicode-le-mcp 全局安装。需要 Node.js。无需环境变量、API 密钥或额外配置,服务器自身不读取文件也不发起网络请求。
安装
在 SourceWeft 中
- 打开 控制台中的 Unicode-LE,将其添加到工作区。
- 为需要使用其工具的对话启用该服务。
Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。
其他 MCP 客户端
参照 仓库 中的启动说明。
README
Unicode-LE: The Characters That Are Not What They Look Like
Find the Unicode that hides meaning in the current file or the whole workspace
Bidi controls, invisibles, homoglyphs, mixed scripts, non-NFC text, spaces that are not the space
[Install from VS Code Marketplace] [Open VSX downloads] [unicode-le-mcp on npm] [unicode-le on crates.io] [LE Tools]
Useful? A star or rating is how other developers find it — ★ GitHub · ★ Open VSX · ★ Marketplace
What it does
Open a file, press Ctrl+Alt+G (Cmd+Alt+G on Mac), and every character in it that is not what it looks like lands in a report beside the editor: the bidirectional controls behind CVE-2021-42574, zero-width and other invisibles, homoglyphs, words no single script accounts for, lines that are not in Normalization Form C, spaces that are not U+0020, and codepoints with no agreed meaning. Scan Workspace does the same for every file on disk. Works in VS Code and in VS Code–based editors like Cursor and VSCodium (installable from Open VSX).
- Review a pull request for Trojan Source — a right-to-left override that makes the code a reviewer reads differ from the code that runs
- Screen for forged names — a Cyrillic
аin an otherwise Latinpаypalis a finding; a word written wholly in Cyrillic is not - Explain the string that never matches — a zero-width space or a decomposed
ébetween two values a hash calls different and a person calls identical
The report never contains a character it found. Every finding is written as U+202E, never as the character itself, and a file path or key path from the document is escaped to . A report that pasted one raw would reorder the screen of whoever read it, and the tool would become the delivery mechanism for the thing it detects. It rewrites nothing: not a normalization, not a stripped space.
Install
Use it from an AI agent
The same engine runs as an MCP server, so an agent can call it directly instead of you running a command. It matters more here than for most tools: a model that pasted a document into its own reasoning has already been handed the bidi controls in it, and what comes back from this server is U+XXXX and English, so the answer cannot carry them on into a commit message or a review comment.
Returns each finding with its kind, severity, 1-based line and column (UTF-16, as an editor counts), byte offset, the codepoints, the script and, where the format allows, the key path it sits under — plus any refusal, both structured and as a warning, so an empty finding list is never mistaken for a clean document. Every non-ASCII character in a reply leaves as \uXXXX. Capped at 500 by default with meta.truncated.
The server takes content and returns data — it reads no files and makes no network requests of its own. Published as unicode-le-mcp on npm and as io.github.nolindnaidoo/unicode-le in the MCP registry. It answers exactly as the Rust CLI's server does: one corpus runs against both, a differential test feeds both thousands of generated documents, and both read the same Unicode tables — written out by the crate — rather than whatever version the host's JavaScript engine carries.
Configuring it by hand — any host with an MCP config file
Or install it once with npm install -g unicode-le-mcp and point at unicode-le-mcp. It needs no environment variables, no API key and no configuration of its own. To check it:
What it finds
It runs on translated code without drowning you
A naive confusable check flags every letter of every Russian and Chinese string in a workspace — thousands of findings on exactly the codebases that most need the check, so it gets switched off, and the Trojan Source screen goes off with it.
- A word is judged, never a file.
Приветis wholly Cyrillic and is Russian. Japanese mixes Han, Hiragana and Katakana in one word constantly, and UTS #39 knows that is Japanese. - A file plainly written in another script is not judged for homoglyphs, and the report says so — at least 10% of its letters in a script nobody declared. Every other check still runs on it.
- Declaring a script turns the check on, never off. Name your workspace's scripts in
unicode-le.detection.scripts—Han,Cyrillic,Hira— and a Latin product name inside a Chinese string is a translation, while a Cyrillic letter in a Latin word in the same file is still caught.
It says where in the document, not just where in the file
In JSON, YAML, TOML, INI and .properties, .env, CSV and TSV a finding also carries the key path it sits under — metrics.headline.eyebrow rather than line 412 of a five-thousand-line catalogue. The format never decides whether a finding exists, only how it is addressed: a file whose format cannot be parsed is still scanned and still reports everything in it.
It refuses rather than guessing
The workspace scan reads every file as bytes. A UTF-16 or UTF-32 file, a binary file and a file that is not valid UTF-8 are refused by name rather than decoded as something else, because a wrong decode invents findings: read UTF-16 as UTF-8 and every second byte becomes an invisible character that is not in the file.
The CLI
The same screen runs from a terminal or a CI step: a Rust CLI in crate/, sharing one corpus with the extension — crate/fixtures/ — so the two can never read a document differently.
Exit codes are the API — 0 clean, 1 a finding --fail-on counts, 2 the question was malformed (or --strict with any refusal).
Commands
Settings
Languages
Twelve languages besides English:
German · Spanish · French · Indonesian · Italian · Japanese · Korean · Portuguese (Brazil) · Russian · Ukrainian · Vietnamese · Chinese (Simplified)
Both halves are covered — the manifest (command titles, setting names and descriptions) and everything shown while the extension runs (notifications, the status bar and the report's headings). Each finding's detail is the engine's English, identical to the CLI's and the MCP server's.
Privacy & security
- No network access. The extension never sends data anywhere. The
telemetryEnabledsetting only writes events to a local Output Channel you can inspect (Unicode-LE). - The MCP server holds the same line. It takes content as an argument and returns data: no filesystem access, no network calls, no telemetry.
check:mcp-bundlefails the build if the server writes a non-ASCII character to stdout. - Error notifications redact home directories and credential-shaped fragments.
Documentation
Performance
Median of 7 runs after warmup, on Apple M5 Pro, 24 GB RAM, Node 24.3.0. Inputs are generated
by scripts/benchmark.ts rather than checked in, so the sizes above are
exactly what was measured. Reproduce with bun run benchmark.
These are machine-specific and are not asserted in CI — a benchmark that gates a build only tells you how busy the runner was.
Testing
117 test cases across 11 files, plus an integration suite that runs
in a real VS Code extension host and an end-to-end test that installs the
built .vsix into a clean profile.
Generated from a real run — coverage/coverage-summary.json and
coverage/test-results.json — by scripts/coverage-readme.js; CI fails if
this section drifts. Reproduce with bun run test:coverage, and the case
count is the one vitest prints.
More from the LE family
Sixteen single-purpose tools for the work in front of every model. Each ships a Rust CLI and an MCP server. One page: letools.dev
Get it out
- String-LE — Extract every string in a codebase, with its position, so a person can read them
- Numbers-LE — Extract every hardcoded number in a codebase, so a person can check them
- Units-LE — Extract every quantity with its unit, normalized, and refuse the ambiguous ones by name
- Dates-LE — Extract every date and timestamp, and the exact instant each one resolves to
- IDs-LE — Extract every UUID, ULID, NanoID, ObjectId and Snowflake, and decode the time inside
- IPs-LE — Extract every IP address, CIDR block and MAC, normalized and classified by scope
- URLs-LE — Extract every URL in a codebase, with its protocol and exact position
- Paths-LE — Extract every file path in a codebase, and say whether it still points at anything
- Colors-LE — Extract every color in a codebase, and say which ones are not in your palette
Check it
- Regex-LE — Find every regex in a codebase, and report which can be driven into catastrophic backtracking
- Versions-LE — Find where one dependency is constrained differently across a repository's manifests
- i18n-LE — Identify the i18n library a project uses, then audit its catalogs by that library's rules
- Scrape-LE — Check whether a page is scrapeable before the scraper is written, and say when it cannot tell
Guard it
- Secrets-LE — Find hardcoded credentials in a codebase, and never print one into the report
- EnvSync-LE — Compare the dotenv files in a tree, and say which keys are missing from which
- Unicode-LE — Find the Unicode that hides meaning — bidi controls, invisibles, homoglyphs, mixed scripts
Each stands on its own: no shared crate, no published core. Where two of them agree, it is because the same answer was right twice.
Contact — nolindnaidoo.com · GitHub · LinkedIn
Also by nolindnaidoo
Rust — pixelcoords and pixelactions are one loop: pixelcoords answers where, pixelactions acts there. Their own tools, their own voice — not part of the LE family.
- pixelcoords — Freeze your screen, mark regions, get pixel-exact coordinates and crops pixelcoords.dev · crates.io · docs.rs
- pixelactions — Consume human-verified coordinates, perform the interaction, confirm it landed pixelactions.dev · crates.io · docs.rs
License
MIT © nolindnaidoo
来源:README.md,提交 d5b9437
工具
0版本历史
1- v1.0.0最新Oct 3, 2026


