关于 Unicode-LE
Unicode-LE 是用于编辑器、终端和 Agent 的 Unicode 风险扫描工具,面向代码审查与配置检查。它标出双向控制符、不可见字符、混合脚本等问题,并以码点报告且不改写文件。
主要功能
- 01检测双向控制符及不可见字符
- 02识别易混字符与混合脚本
- 03检查非 NFC 文本及异常空格
- 04扫描当前文件、文件夹或工作区
- 05报告以码点表示,不改写内容
适用场景
- 01审查代码中的 Trojan Source 风险
- 02排查相似字符伪造名称
- 03分析字符串匹配或哈希差异
- 04在 CI 中扫描整个代码树
MCP 相关
MCP 服务提供 detect_unicode_risks 工具,接收文本并返回风险发现,不读取文件或发起网络请求。回复中的非 ASCII 字符以转义形式呈现。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
1收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| kinds | array | 否 | 仅报告这些类型。省略此参数表示报告所有类型,这是空结果意味着未发现问题的唯一设置。 |
| format | enum | 否 | 文档格式,因此每条发现都可以包含指明其所在位置的键路径。省略此参数时,发现结果仅包含行号和列号;名称无法识别时,将不返回键路径,但不会影响发现结果。json | yaml | csv | tsv | toml | ini | env | text |
| content | string | 是 | 要扫描的文档文本。 |
| scripts | array | 否 | 此文档预期使用的非拉丁文字体系,可填写 Unicode 名称或 ISO 15924 标签,例如 Han 或 Cyrillic。指定文字体系后,才能对翻译后的文档运行形近字符检查。 |
| filename | string | 否 | 文档名称,可替代 `format`。仅读取扩展名,不会打开文件。 |
| maxResults | integer | 否 | 返回的问题数上限(默认 500)。meta.truncated 报告是否有结果被省略。 |
详细介绍
Unicode-LE:看起来并非如此的字符
在当前文件或整个工作区中,查找隐藏含义的 Unicode 字符
双向控制符、不可见字符、同形异义字符、混合文字、非 NFC 文本,以及看起来不像空格的空格
觉得有用? 点赞或评分能帮助其他开发者找到它 — ★ GitHub · ★ Open VSX · ★ Marketplace
功能
打开文件,运行 Unicode-LE: Detect Unicode Risks,文件中所有看起来并非如此的字符都会出现在编辑器旁的报告中:CVE-2021-42574 背后的双向控制符、零宽字符和其他不可见字符、同形异义字符、无法用单一文字系统解释的词语、非规范化形式 C 的行、非 U+0020 的空格,以及含义尚无定论的码点。Scan Workspace 会对磁盘上的每个文件执行同样的检查。适用于 VS Code,以及 Cursor 和 VSCodium 等基于 VS Code 的编辑器(可从 Open VSX 安装)。
- 审查拉取请求,检查是否存在 Trojan Source — 从右向左覆盖符会使审查者看到的代码与实际运行的代码不同
- 筛查伪造名称 — 在其他字符均为拉丁字母的
pаypal中出现西里尔字母а会被标记;完全由西里尔字母写成的词则不会 - 解释永远无法匹配的字符串 — 两个值之间的零宽空格或分解形式的
é,会让哈希认为它们不同,而人认为它们相同
报告中绝不会出现检测到的字符。 每个发现都写作 U+202E,绝不会直接写出字符本身;文档中的文件路径或键路径也会转义为 。如果报告直接粘贴原始字符,就会扰乱阅读者屏幕上的文本顺序,让工具沦为它所检测问题的传播媒介。它不会改写任何内容:不会规范化,也不会删除空格。
安装
| 位置 | 可获得的功能 | 安装方式 |
|---|---|---|
| VS Code | 在编辑器中按一下快捷键即可查看 | Marketplace |
| Cursor、VSCodium、Windsurf | 同一扩展 | Open VSX |
| 终端或 CI 步骤 | 扫描整个目录树并返回相同结果,同时提供退出代码 | cargo install unicode-le · crates.io |
| 通过 Node 使用终端 | 提供相同的命令行,与 Rust 版本逐字节保持一致 | npx unicode-le · npm |
| 通过 Node 使用任意 MCP Agent | 通过标准输入输出提供 detect_unicode_risks | npx unicode-le-mcp · npm |
在 AI Agent 中使用
同一引擎也可作为 MCP 服务运行,因此 Agent 可以直接调用它,无需你运行命令。这对这里的情况尤为重要:模型若已将文档粘贴到自己的推理过程中,就已经接触到其中的双向文本控制符;而此服务返回的内容是 U+XXXX 和英文,因此回答不会将这些控制符带入提交消息或审查评论。
| 编辑器 | 方法 |
|---|---|
| VS Code 1.101+ | 无需安装 — 此扩展会在 Agent 模式下注册 detect_unicode_risks |
| Claude Code | claude mcp add unicode-le -- npx -y unicode-le-mcp |
| Cursor、Windsurf 及其他工具 | 将其指向 npx unicode-le-mcp |
detect_unicode_risks(content, kinds?, scripts?, format?, filename?, maxResults?)
每条发现都会返回其类型、严重性、从 1 开始的行号和列号(按编辑器计数的 UTF-16)、字节偏移量、码点、文字,以及在格式允许时它所属的键路径;还会返回任何拒绝信息,既包括结构化内容,也包括警告,避免将空的发现列表误认为文档没有问题。回复中的每个非 ASCII 字符都会以 \uXXXX 形式输出。默认最多返回 500 条,并通过 meta.truncated 标明是否截断。
此服务接收内容并返回数据 — 不读取文件,也不会自行发起网络请求。它以 unicode-le-mcp 的名称发布在 npm 上,并以 io.github.nolindnaidoo/unicode-le 的名称收录于 MCP 注册表。它的回答与 Rust CLI 服务完全一致:同一测试语料会同时用于两者,差异测试会将数千份生成的文档输入两者进行比对,而且两者读取的都是由该 crate 写出的同一套 Unicode 表,而不是宿主 JavaScript 引擎自带的版本。
手动配置 — 适用于任何拥有 MCP 配置文件的宿主
{
"mcpServers": {
"unicode-le": {
"command": "npx",
"args": ["-y", "unicode-le-mcp"]
}
}
}
或者通过 npm install -g unicode-le-mcp 一次性安装,然后将其指向 unicode-le-mcp。它不需要环境变量、API 密钥或任何额外配置。运行以下命令进行检查:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | npx -y unicode-le-mcp
检查内容
| 类型 | 严重性 | 说明 |
|---|---|---|
bidi-control | 高 | U+202A–U+202E、U+2066–U+2069、U+061C — Trojan Source 类控制符。它们会改变该行其余部分的显示顺序。 |
confusable | 高 | 其他文字中的单词里出现的形近字符,或 ASCII 字符的兼容形式(全角 F、数学字母 𝐚、开尔文符号),并提供其相似字符对应的码点 |
mixed-script | 高 | 无法归属于单一文字的单词 |
invisible | 中 | 零宽字符、软连字符,以及出现在首字节以外位置的 U+FEFF |
unassigned-or-private-use | 中 | 没有指定含义的码点:专用区、未分配码点、非字符 |
non-nfc | 低 | 不符合规范化形式 C 的行。只报告,不会改写 |
unusual-whitespace | 低 | 非 U+0020 的空格:不换行空格、表意空格、半角和全角空格 |
它能检查翻译后的代码,而不会让你应接不暇
简单的易混淆字符检查会标记工作区中俄语和中文字符串里的每个字母——在最需要这项检查的代码库中,这会产生数千条发现,因此检查被关闭,Trojan Source 检查也随之关闭。
- 判断的是词,而不是文件。
Привет完全由西里尔字母组成,是俄语。日语词语中经常混用汉字、平假名和片假名,而 UTS #39 知道这是日语。 - 明显使用其他文字书写的文件不会接受同形异义字符检查,报告也会说明原因——其中至少 10% 的字母属于未声明的文字。其他所有检查仍会在该文件上运行。
- 声明文字会启用检查,而不是关闭检查。 在
unicode-le.detection.scripts中指定工作区使用的文字——Han、Cyrillic、Hira——这样,中文字符串中的拉丁文产品名称会被视为翻译,而同一文件中拉丁词里的西里尔字母仍会被捕获。
它会指出文档中的位置,而不只是文件中的位置
在 JSON、YAML、TOML、INI、.properties、.env、CSV 和 TSV 文件中,发现项还会包含其所在的键路径——metrics.headline.eyebrow,而不是五千行目录中的第 412 行。格式不会决定是否存在发现项,只决定如何标注位置:无法解析格式的文件仍会接受扫描,其中的所有内容仍会报告。
它会拒绝处理,而不是妄加猜测
工作区扫描会将每个文件读取为字节。UTF-16 或 UTF-32 文件、二进制文件以及无效 UTF-8 文件都会按名称拒绝处理,而不会被解码成其他内容,因为错误的解码会凭空产生发现项:将 UTF-16 作为 UTF-8 读取时,每隔一个字节都会变成文件中并不存在的不可见字符。
扫描文件夹或工作区
检测会读取当前打开的文档。扫描会从磁盘读取多个文件,并生成一份报告。
- 整个工作区:从命令面板运行
Unicode-LE: Scan Workspace for Unicode Risks。 - 单个文件夹:在资源管理器中右键单击该文件夹,然后选择
Scan Folder for Unicode Risks;或者运行Unicode-LE: Scan Folder for Unicode Risks并选择一个文件夹。
报告会为每个包含问题的文件以及每个无法判定的文件分别列出一节:
# Unicode-LE report
3 finding(s) in 4 file(s) scanned; 2 file(s) not fully judged.
## `latin1.txt`
- **Not judged** (`binary_or_undecodable`): not valid UTF-8 at byte 3: the encoding is unknown and is not guessed
## `notes.txt`
- **Not judged** (`encoding_unknown`): a UTF-16LE byte-order mark: this reads UTF-8 only, and decoding UTF-16LE as UTF-8 would invent invisible and unassigned characters that are not in the file
## `src/login.ts`
- **1:7** · mixed-script · high · U+0430
- **1:8** · confusable · high · U+0430 (resembles U+0061)
- **1:17** · bidi-control · high · U+202E
每个问题还会附上一句说明其重要性的文字,此处省略。
扫描会读取什么。 文件从磁盘读取,因此不会看到尚未保存的编辑。超过安全大小限制的文件不会被读取。非 UTF-8 文本文件会在报告中列出,并说明原因;绝不会猜测其编码,也不会跳过不提。扫描最多处理 5,000 个文件,或列出 10,000 个问题。报告末尾会逐项列出所有被排除的内容。
扫描跳过文件并不代表检查通过。 默认情况下,扫描不会读取依赖文件夹、项目的 .gitignore 排除的文件或二进制文件。隐藏在其中的字符不会被报告。若要检查所有内容,请关闭这三个开关。
扫描会跳过什么,以及如何更改。 默认情况下,三个开关均已启用,可在设置中分别关闭:
| 开关 | 跳过内容 |
|---|---|
scanUseDefaultExcludes | 依赖文件夹、构建输出、工具缓存和锁文件。完整列表如下 |
scanRespectGitignore | 项目的 .gitignore 文件排除的所有内容 |
scanSkipBinaryFiles | 图像、字体、存档文件及其他非文本文件 |
两个列表可以调整扫描结果,而无需关闭开关。若要跳过更多内容,请将模式添加到 scanExcludes。若要读取开关本会跳过的内容,请将其添加到 scanAlwaysInclude:
{
// Also skip the test fixtures.
"unicode-le.workspace.scanExcludes": ["**/fixtures/**"],
// Read the vendored code, though the built-in list skips it.
"unicode-le.workspace.scanAlwaysInclude": ["**/vendor/**"]
}
Unicode-LE: Open Settings 会在设置编辑器中打开上述所有设置。
内置列表
文件夹(无论出现在哪里):
.git、.hg、.svn、node_modules、bower_components、jspm_packages、.pnpm-store、.yarn、vendor、site-packages、Pods、Carthage、dist、build、out、target、_build、_site、dist-newstyle、zig-out、storybook-static、cdk.out、DerivedData、CMakeFiles、.next、.nuxt、.output、.svelte-kit、.angular、.astro、.docusaurus、.vuepress、.expo、.turbo、.parcel-cache、.cache、.sass-cache、.jekyll-cache、.dart_tool、.pub-cache、.gradle、.kotlin、.cxx、.externalNativeBuild、captures、ephemeral、.symlinks、.swiftpm、.build、.bundle、.stack-work、.zig-cache、.godot、elm-stuff、.vercel、.netlify、.serverless、.aws-sam、.terraform、.venv、venv、__pycache__、.tox、.nox、.mypy_cache、.pytest_cache、.ruff_cache、.ipynb_checkpoints、.eggs、coverage、htmlcov、.nyc_output、.vscode-test、.idea、.vs、xcuserdata、*.egg-info
文件(无论出现在哪里):
*.min.js、*.min.css、*.map、*.snap、*.lock、package-lock.json、pnpm-lock.yaml、npm-shrinkwrap.json、go.sum、*.pbxproj、*.iml、local.properties、output-metadata.json、.flutter-plugins、.flutter-plugins-dependencies、.packages、Generated.xcconfig、flutter_export_environment.sh、GeneratedPluginRegistrant.*、fastlane/report.xml、fastlane/test_output/**、doc/api/**
未列入其中的文件夹(因为它们在许多项目中都是普通文件夹):bin、obj、tmp、logs、public、generated。如果项目会生成这些文件夹,通常会在 git 中忽略它们;扫描会读取 .gitignore。
在“问题”面板中。 启用 unicode-le.workspace.scanProblemsEnabled 后,每个问题也会作为警告显示在对应行上。每次扫描都会替换上一次扫描的结果。消息会显示码位,而不是字符。
影响扫描的设置位于设置下。
命令行工具
同一屏幕也可以在终端或 CI 步骤中运行:一个位于 crate/ 中的 Rust CLI,与扩展共用同一份语料库——crate/fixtures/——因此两者读取文档的方式始终一致。同一命令行工具也以 unicode-le 的形式发布在 npm 上,适用于安装了 Node、但没有 Rust 的机器;CI 会在三个操作系统上逐字节验证它与 crate 一致。
从 npm (unicode-le) 或 crates.io (unicode-le) 安装。 两者都会安装同一个 unicode-le 命令。
npm install -g unicode-le # or run it once: npx unicode-le
cargo install unicode-le
unicode-le . # every finding in the tree; JSON on stdout when piped
unicode-le --fail-on bidi . # in CI, for the CVE and nothing else
unicode-le --script Han,Cyrillic src/ # a translated tree, judged rather than refused
unicode-le mcp # the same screen over MCP on stdio
退出代码就是 API——0 表示无问题,1 表示发现了 --fail-on 会计入的问题,2 表示问题格式错误(或使用 --strict 时遇到任何拒绝项)。
命令
| 命令 | 说明 |
|---|---|
Unicode-LE: Detect Unicode Risks | 按编辑器当前持有的内容检查活动文档 |
Unicode-LE: Scan Workspace for Unicode Risks | 检查工作区中的每个文件,并以 UTF-8 格式从磁盘读取 |
Unicode-LE: Scan Folder for Unicode Risks | 对单个文件夹执行相同检查。也可在资源管理器中的文件夹上使用 |
Unicode-LE: Open Settings | 打开 Unicode-LE 设置 |
Unicode-LE: Help & Troubleshooting | 内置文档 |
默认情况下,没有命令绑定快捷键。可在编辑器的 键盘快捷方式 中为任意命令设置快捷键。
设置
| 设置项 | 默认值 | 描述 |
|---|---|---|
unicode-le.detection.kinds | [] | 仅报告这些类型;为空表示所有类型,这是唯一一种空报告表示干净的设置 |
unicode-le.detection.scripts | [] | 文件中使用的非拉丁文字,以 Unicode 名称或 ISO 15924 标签表示 |
unicode-le.openResultsSideBySide | true | 在当前编辑器旁打开报告 |
unicode-le.showPositions | true | 显示每个发现项所在的行和列 |
unicode-le.copyToClipboardEnabled | false | 同时将报告复制到剪贴板 |
unicode-le.clipboardIncludesPositions | true | 在复制内容中包含行和列 |
unicode-le.workspace.scanPatterns | ["**/*"] | 文件夹或工作区扫描读取的文件 |
unicode-le.workspace.scanUseDefaultExcludes | true | 跳过依赖文件夹、构建输出、缓存和锁文件 |
unicode-le.workspace.scanRespectGitignore | true | 跳过项目的 .gitignore 文件排除的内容 |
unicode-le.workspace.scanSkipBinaryFiles | true | 跳过图像、字体、归档文件和其他非文本文件 |
unicode-le.workspace.scanExcludes | [] | 以 glob 模式指定要跳过的其他文件 |
unicode-le.workspace.scanAlwaysInclude | [] | 即使上述三项设置会跳过,也要读取的文件 |
unicode-le.workspace.scanMaxFiles | 5000 | 单次扫描读取的文件数上限 |
unicode-le.workspace.scanMaxResults | 10000 | 停止读取前,单次扫描列出的发现项上限 |
unicode-le.workspace.scanProblemsEnabled | false | 同时在“问题”面板中显示扫描发现项 |
unicode-le.notificationsLevel | silent | all = 所有通知,important = 警告 + 错误,silent = 仅错误 |
unicode-le.safety.enabled | true | 大文件防护措施 |
unicode-le.safety.fileSizeWarnBytes | 1000000 | 对较大的活动文档发出警告;不读取更大的工作区文件 |
unicode-le.statusBar.enabled | true | 显示状态栏项 |
unicode-le.telemetryEnabled | false | 仅限本地的事件日志(参见“隐私”) |
语言
除英语外,支持十二种语言:
德语 · 西班牙语 · 法语 · 印度尼西亚语 · 意大利语 · 日语 · 韩语 · 葡萄牙语(巴西) · 俄语 · 乌克兰语 · 越南语 · 简体中文
两部分内容均已覆盖:扩展清单(命令标题、设置名称和描述),以及扩展运行期间显示的所有内容(通知、状态栏和报告标题)。每个发现项的详细信息使用引擎的英文,与 CLI 和 MCP 服务中的内容一致。
隐私与安全
- 不访问网络。 扩展绝不会向任何地方发送数据。
telemetryEnabled设置只会将事件写入你可查看的本地输出通道(Unicode-LE)。 - MCP 服务遵循相同原则。 它将内容作为参数接收并返回数据:不访问文件系统、不进行网络调用、不收集遥测数据。若服务向 stdout 写入非 ASCII 字符,
check:mcp-bundle会导致构建失败。 - 错误通知会隐藏主目录和类似凭据的片段。
- 评分提示最多出现两次。 第 3 次成功使用时,扩展会询问你是否愿意为其评分;如果你选择了 Later 或关闭提示,则会在第 20 次使用时再询问一次。选择 Don't Ask Again 后将不再提示。你自行将
notificationsLevel设为important或silent也会关闭提示。计数保存在 VS Code 的扩展存储中,不会向任何地方发送;点击 Rate 会在浏览器中打开你安装时所用的列表页面——VS Code Marketplace 或 Open VSX。
文档
| 内容 | 位置 |
|---|---|
| 工具允许说什么——范围、输出约定、拒绝事项、非目标 | crate/SPEC.md |
| 扩展如何构建和运作——架构、不变量、工具链、发布 | AGENTS.md |
| CLI 如何构建和运作 | crate/AGENTS.md |
| 变更内容 | CHANGELOG.md · crate/CHANGELOG.md |
| 工具页面及另外 15 个工具 | letools.dev/tools/unicode-le |
性能
| 输入 | 大小 | 发现数 | 耗时 | 速率 | 扫描速度 |
|---|---|---|---|---|---|
| 含风险内容的源文件 | 1.16 MB | 1,800 | 160.71 ms | 11,200/sec | 7.2 MB/s |
| JSON 目录 | 1.46 MB | 20,000 | 195.09 ms | 102,518/sec | 7.5 MB/s |
| 压缩后的单行内容 | 0.63 MB | 40,000 | 78.7 ms | 508,247/sec | 8 MB/s |
在 Apple M5 Pro、24 GB RAM、Node 24.3.0 上预热后运行 7 次所得的中位数。输入由
scripts/benchmark.ts 生成,而非纳入版本控制,因此上方所列大小
与实际测量值完全一致。使用 bun run benchmark 重现。
这些数据因机器而异,不会在 CI 中强制要求——用基准测试来阻止 构建,只能说明运行器当时有多忙。
测试
| 指标 | 覆盖率 |
|---|---|
| 语句 | 79.26% |
| 分支 | 71.22% |
| 函数 | 82.30% |
| 行 | 81.80% |
19 个文件中共有 224 个测试用例,另有一套在真实 VS Code 扩展宿主中运行的集成测试,以及一项将构建好的 .vsix 安装到干净配置文件中的端到端测试。
数据由 scripts/coverage-readme.js 根据实际运行生成,来源为 coverage/coverage-summary.json 和
coverage/test-results.json;如果本节内容发生偏差,CI 会失败。使用 bun run test:coverage 重现,测试用例数以 vitest 输出为准。
LE 系列的更多工具
为每个模型眼前的工作打造的 16 款单一用途工具。每款都附带一个 Rust CLI 和一个 MCP 服务。一页尽览:letools.dev
提取
- String-LE — 提取代码库中的所有字符串,方便人工阅读
- Numbers-LE — 提取代码库中所有硬编码数字,方便人工检查
- Units-LE — 提取所有带单位的数量并统一格式,遇到含义不明确的数量时会按名称拒绝处理
- Dates-LE — 提取所有日期和时间戳,并给出每个日期和时间戳解析出的确切时刻
- IDs-LE — 提取所有 UUID、ULID、NanoID、ObjectId 和 Snowflake,并解码其中的时间
- IPs-LE — 提取所有 IP 地址、CIDR 网段和 MAC 地址,统一格式并按范围分类
- URLs-LE — 提取代码库中的所有 URL,并给出协议和确切位置
- Paths-LE — 提取代码库中的所有文件路径,并说明它们是否仍指向实际内容
- Colors-LE — 提取代码库中的所有颜色,并指出哪些颜色不在你的调色板中
检查
- Regex-LE — 查找代码库中的所有正则表达式,并报告哪些可能导致灾难性回溯
- Versions-LE — 查找某个依赖项在仓库的各个清单文件中受到不同约束的位置
- i18n-LE — 识别项目使用的 i18n 库,然后依据该库的规则审查其目录
- Scrape-LE — 在编写抓取器之前检查网页能否抓取,并说明何时无法判断
防护
- Secrets-LE — 查找代码库中的硬编码凭据,绝不在报告中打印凭据
- EnvSync-LE — 比较目录树中的 dotenv 文件,并指出哪些文件缺少哪些键
- Unicode-LE — 查找那些隐藏含义的 Unicode 字符——双向控制字符、不可见字符、同形异义字符、混合文字系统
每款工具都独立运行:没有共享的 crate,也没有发布的核心库。若有两款工具得出相同结论,那是因为这个答案经得起两次验证。
联系 — nolindnaidoo.com · GitHub · LinkedIn
nolindnaidoo 的其他项目
Rust — pixelcoords 和 pixelactions 构成一个工作循环:pixelcoords 回答 位置,pixelactions 在那里执行操作。它们有各自的工具、各自的风格——不属于 LE 系列。
- pixelcoords — 冻结屏幕,标记区域,获取像素级精确的坐标和裁剪图像 pixelcoords.dev · crates.io · docs.rs
- pixelactions — 使用经过人工验证的坐标执行交互,并确认操作已生效 pixelactions.dev · crates.io · docs.rs
许可证
MIT © nolindnaidoo
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。