Semgrep Aliengrep 引擎实战:以 Markdown 文档测试目标为例的通用文本模式匹配指南
Semgrep Aliengrep 引擎实战:以 Markdown 文档测试目标为例的通用文本模式匹配指南
本指南以仓库中 cli/tests/default/e2e/targets/aliengrep/markdown.md 这份文档文件为切入点,深入讲解 Semgrep 的 Aliengrep 通用文本匹配引擎:它如何用"看起来像源代码"的规则扫描 Markdown、HTML、日志等非代码文本,以及如何在 Semgrep 规则中配置启用。读完本文,你将掌握 Aliengrep 的模式语法、元变量与省略号规则、单行/多行模式差异,并能独立编写并验证属于自己的通用文本检测规则。
关联文档的真实身份:一份 Aliengrep 测试目标文件
在深入讲解之前,有必要先澄清这份文档在仓库中的定位。路径 cli/tests/default/e2e/targets/aliengrep/markdown.md 表明它位于 Semgrep 的端到端(E2E)测试目录中,是 Aliengrep 引擎的测试输入文件(target),而不是一篇面向读者的独立技术文档。
它的内容是一份完整、可运行的 Markdown 文件(Semgrep 项目自述文档的早期形态),恰好适合作为"非结构化文本"扫描的真实样本:其中既有 # 一级标题、## 二级标题、### 三级标题等 Markdown 结构,也有代码块、表格、徽章图片链接等多种 Markdown 元素,能充分检验通用文本引擎的匹配能力。
它被测试框架用作验证目标的证据链非常清晰:
- 规则文件
cli/tests/default/e2e/rules/aliengrep/markdown.yaml定义了一条专门检测 Markdown 二级标题的规则header-2; - 测试驱动器
cli/tests/default/e2e/test_aliengrep.py第 24 行将("rules/aliengrep/markdown.yaml", "aliengrep/markdown.md")作为参数组合传入test_aliengrep测试用例; - 测试快照
cli/tests/default/e2e/snapshots/test_aliengrep/test_aliengrep/rulesaliengrepmarkdown.yaml-aliengrepmarkdown.md/results.json记录了规则在此目标上的全部匹配结果。
也就是说,本文既是一份"README 级"的 Semgrep 使用指南(继承自该文件正文),又是一份"测试用例级"的 Aliengrep 引擎剖析(源于该文件在仓库中扮演的角色)。
先看结果:## $TEXT 规则如何在 Markdown 上命中 6 处标题
在动手讲解规则语法之前,先看这条测试规则的完整定义(cli/tests/default/e2e/rules/aliengrep/markdown.yaml):
rules:
- id: header-2
severity: INFO
languages: [generic]
options:
generic_engine: aliengrep
message: Detected h2 Markdown header
patterns:
- pattern: "## $TEXT"
- pattern-not-inside: "### $TEXT"
逐字段解读:
| 配置项 | 值 | 作用 |
|---|---|---|
id |
header-2 |
规则唯一标识,在输出中以 rules.aliengrep.header-2 形式出现 |
severity |
INFO |
命中时的告警级别 |
languages |
[generic] |
指定扫描的目标语言为 generic(通用模式),由通用引擎处理 |
options.generic_engine |
aliengrep |
核心开关:指定通用模式使用 Aliengrep 引擎(而非默认的 Spacegrep) |
pattern |
"## $TEXT" |
匹配以 ## 开头的 Markdown 二级标题行,$TEXT 是捕获标题文本的元变量 |
pattern-not-inside |
"### $TEXT" |
排除位于三级标题内部的匹配,避免二级标题模式误命中 ### 的行 |
该规则在目标文件 markdown.md 上运行时,快照文件 results.json 记录了 6 处命中,全部来自 ## 开头的二级标题行:
| 命中行号 | 匹配内容 | 说明 |
|---|---|---|
| 第 62 行 | ## Getting Started |
命中 |
| 第 97 行 | ## Examples |
命中 |
| 第 145 行 | ## Resources |
命中 |
| 第 159 行 | ## Usage |
命中 |
| 第 188 行 | ## Contributing |
命中 |
| 第 203 行 | ## Commercial Support |
命中 |
而被 ### Try it out、### Command Line Options、### Exit Codes、### Upgrading 等 ### 三级标题命中的行并未出现在结果中——这正是 pattern-not-inside: "### $TEXT" 发挥的排除作用。快照还显示出 ## Getting Started 等标题行内嵌的 <a> 链接文本并未干扰匹配,说明 Aliengrep 的行级匹配与元变量捕获是精确且局部的。
Aliengrep 是什么:为文本匹配而生的通用引擎
在 Semgrep 中,generic 语言(又称通用模式)用于扫描无法用常规编程语言解析器处理的文件格式。仓库 src/aliengrep/README.md 开宗明义:Aliengrep 是一个通用模式引擎,设计初衷是弥补 Spacegrep(Semgrep 传统 generic 模式实现)的不足,提供一种"比正则更简单、更 Semgrep 友好"的方式来编写针对文本目标的匹配模式。
它被定位为"像 grep 一样快和灵活,同时保留传统静态分析工具的精确性"的产物——这与该测试目标文档正文中"Combines the speed and customization of grep with the precision of traditional static analysis tools"的描述完全呼应。
一个直观示例:hello($...ARGS)
src/aliengrep/README.md 给出的经典示例是模式 hello($...ARGS),它可以匹配:
hello(first_name, last_name)
也能匹配各种"形状不同但语义相同"的写法:
hello ()
以及跨多行的写法:
hello(
get_name()
)
还能匹配:
hello(get_name())
这种"写起来像源码、匹配时忽略格式差异"的能力,正是 Aliengrep 区别于正则表达式的核心价值:规则作者无需关心目标文本的缩进与换行细节。
与 Spacegrep 的六大差异
src/aliengrep/README.md 的 Features 章节系统列出了 Aliengrep 与 Spacegrep 的主要区别:
- 缩进无关:模式或目标代码中的缩进不影响匹配,所有空白都被视为可选的(可选)。
- 可定制词法与括号字符集:构成"单词"和"括号"的字符集合可自定义。
- 省略号无行数限制:
...(多行模式)或....(单行模式)可跨越任意行数。 - 单行模式的换行是字面量:在单行模式下,模式中的换行符按字面匹配,普通省略号
...不会跨换行。 - 长省略号跨行(实验性):单行模式下,四点的长省略号
....或$....FOO允许把换行当作普通空白匹配。 - UTF-8 兼容:与 Spacegrep 不同,模式与目标代码必须是 UTF-8 兼容的。
其他特性还包括:与 Semgrep/Spacegrep 一致的省略号(...)与元变量($FOO、$...FOO)支持;普通元变量 $FOO 捕获单个单词;词、非词字符、括号之间的空白被忽略、仅作为词法元素之间的分隔符;要字面匹配 $FOO 或 ... 等特殊字符序列,需用空格隔开写成 $ FOO 或 . . .;括号尽可能成对匹配,例如模式 (...) 匹配 ((x), y) 而非仅 ((x)。
规则中的引擎开关:generic_engine: aliengrep
从源码看,Aliengrep 是通过规则 options 段指定的通用引擎。在 src/parsing/Parse_rule.ml 中,规则解析器会检查 options.generic_engine 字段(第 100 行),并将其映射到具体的分析器:第 591 行 ("generic" | "spacegrep"), _ -> Ok LSpacegrep 说明默认的 generic 引擎是 Spacegrep;第 592 行 "aliengrep", _ -> Ok LAliengrep 则说明显式声明 generic_engine: aliengrep 会启用 Aliengrep 引擎。
因此一个最小可用的 Aliengrep 规则骨架是:
rules:
- id: my-text-rule
severity: WARNING
languages: [generic]
options:
generic_engine: aliengrep
generic_multiline: true # 可选:切换多行模式
message: found something
pattern: "begin ... end"
其中 generic_multiline 用于切换多行模式(true)与单行模式(false)。仓库中的另一条测试规则 cli/tests/default/e2e/rules/aliengrep/begin-end.yaml 就演示了 generic_multiline: false 的写法,并用模式 begin ... end 检测以 begin 开头、以 end 结尾的代码块。
模式编译:Aliengrep 如何把模式变成 PCRE2 正则
Aliengrep 的实现并不直接使用 ocamllex 词法分析器,而是用自定义解析器解析模式,再将其翻译为 PCRE2 正则表达式;目标代码本身不做解析,直接作为扫描对象(见 src/aliengrep/README.md 的 Implementation 章节)。这条链路的核心实现在 src/aliengrep/Pat_compile.ml:
- 模式解析:
Pat_parser.from_string将模式字符串解析为 AST; - 正则编译:
compile调用to_regexp生成 PCRE2 模式,并记录"元变量捕获组"映射metavariable_groups(从 PCRE 捕获组编号到元变量的映射); - 匹配执行:
Match.search在目标字符串上执行扫描,返回匹配位置与捕获列表(见src/aliengrep/Match.mli)。
Pat_compile.ml 中的若干细节值得注意:
- 单词定义:
def_word使用(?&lwb)%s++(其中%s由conf.word_chars生成字符类),即"左词边界 + 单词字符";def_lwb/def_rwb用前后视断言实现词边界,保证元变量匹配的是完整单词而非子串。 - 可配置空白:单行模式使用
[[:blank:]]*+(不含换行),多行模式使用[[:space:]]*+(含换行),见singleline_spacing_param与multiline_spacing_param。 - 括号成对匹配:
def_bracket为每种括号对(来自conf.brackets)生成开括号 ... 闭括号的子程序,通过排除特定闭括号字符(excluded_brace)防止模式(...x)误匹配()x)这类畸形括号。 - 省略号的原子性:省略号子程序用
(?>(?&node))原子组包裹,防止 PCRE2 10.30+ 回溯进子程序调用导致跨块巨匹配(源码注释详细记录了这一兼容性问题)。
配置结构定义在 src/aliengrep/Conf.mli:caseless(大小写不敏感)、multiline(换行视为普通空白)、word_chars(单词字符集,目前 TODO 注释说明暂不支持 UTF-8 单词字符)、brackets(成对括号列表)。默认配置分 default_multiline_conf 与 default_singleline_conf 两套,对应两种运行模式。
结合文档正文的实操指引:安装、运行与退出码
关联文档正文还提供了完整的 Semgrep 安装与使用指引,以下是继承自该文档并补充了当前仓库信息的实操内容。
安装 Semgrep
Semgrep 支持 brew、pip、docker 三种方式:
# macOS
$ brew install semgrep
# Ubuntu/WSL/Linux,推荐 pip
$ python3 -m pip install semgrep
# 不想安装?用 Docker 尝试
$ docker run --rm -v "${PWD}:/src" returntocorp/semgrep --help
安装完成后用 semgrep --help 确认安装成功并查看全部命令行选项(关联文档明确指出:完整的命令行选项清单以 semgrep --help 输出为准)。
运行规则:单模式与规则集
# 用单条规则模式扫描 Python 代码:检测左右相同的 == 比较(往往是 bug)
$ semgrep -e '$X == $X' --lang=py path/to/src
# 用规则集扫描,覆盖多种语言
$ semgrep --config=https://semgrep.dev/p/r2c-CI path/to/src
对本文主题而言,运行 Aliengrep 规则的完整命令形如:
$ semgrep --config=rules/aliengrep/markdown.yaml targets/aliengrep/markdown.md
这正是 cli/tests/default/e2e/test_aliengrep.py 中 test_aliengrep 测试所做的事情:它调用 run_semgrep_in_tmp(rule, target_name=target) 执行规则,再用 posix_snapshot.assert_match(..., "results.json") 与快照结果比对,确保规则行为不回归。仓库内同一目录还提供了更多 Aliengrep 测试样本,例如 cli/tests/default/e2e/targets/aliengrep/html.mustache(HTML 模板)、cli/tests/default/e2e/targets/aliengrep/dockerfile(Dockerfile)、cli/tests/default/e2e/targets/aliengrep/terraform.tf(Terraform)、cli/tests/default/e2e/targets/aliengrep/httpresponse.txt(HTTP 响应文本)、cli/tests/default/e2e/targets/aliengrep/multi-lines.java(多行 Java)以及 cli/tests/default/e2e/targets/aliengrep/long-match.txt(长匹配文本),这些目标文件各自配对了 cli/tests/default/e2e/rules/aliengrep/ 下的同名 YAML 规则,是理解 Aliengrep 能力边界的绝佳素材。
退出码语义
Semgrep 命令可能以如下退出码结束:
0:Semgrep 运行成功,未发现任何问题1:Semgrep 运行成功,但在代码中发现问题>= 2:Semgrep 运行失败
这一约定对 CI 集成至关重要:把退出码 1 视为"发现告警"而非"报错",即可将 Semgrep 无缝嵌入流水线。
升级 Semgrep
# Homebrew
$ brew upgrade semgrep
# pip
$ python3 -m pip install --upgrade semgrep
# Docker
$ docker pull returntocorp/semgrep:latest
复现与验证:把这条 Aliengrep 测试跑起来
若想亲自复现本文的所有结论,可遵循以下步骤(仓库只读,以下均为查看与运行操作):
- 查看规则:阅读
cli/tests/default/e2e/rules/aliengrep/markdown.yaml,理解header-2规则的模式与排除逻辑; - 查看目标:阅读
cli/tests/default/e2e/targets/aliengrep/markdown.md,逐行对照其中的##/###标题; - 运行规则(需已安装 semgrep):
$ semgrep --config=cli/tests/default/e2e/rules/aliengrep/markdown.yaml cli/tests/default/e2e/targets/aliengrep/markdown.md
- 对照快照:将输出与
cli/tests/default/e2e/snapshots/test_aliengrep/test_aliengrep/rulesaliengrepmarkdown.yaml-aliengrepmarkdown.md/results.json比对,应恰好得到 6 条header-2命中,且###三级标题行不被报告。
如果希望深入引擎实现,可继续阅读 src/aliengrep/README.md、src/aliengrep/Pat_compile.ml、src/aliengrep/Pat_parser.ml 与 src/aliengrep/Match.ml,并结合测试驱动器 cli/tests/default/e2e/test_aliengrep.py 理解端到端行为。
小结
这份"看起来只是 Markdown 文档"的 cli/tests/default/e2e/targets/aliengrep/markdown.md,实际上是 Semgrep Aliengrep 引擎的活体测试样本。通过它,我们可以完整串起 Aliengrep 的三大知识层:
- 规则层:在
languages: [generic]的规则中声明options.generic_engine: aliengrep,即可用## $TEXT这类"长得像源码"的模式扫描任意文本; - 引擎层:Aliengrep 通过自定义解析器把模式翻译为 PCRE2 正则(
src/aliengrep/Pat_compile.ml),支持元变量、省略号、括号成对匹配、单行/多行空白语义等特性; - 验证层:端到端测试(
cli/tests/default/e2e/test_aliengrep.py)与快照(results.json)为引擎行为提供了可复现的回归保障。
掌握这套"规则 + 引擎 + 测试"的配合方式后,你就能为 Markdown、HTML、日志、配置文件等任何半结构化文本编写精确、易维护的检测规则,这正是 Aliengrep 相比正则表达式所承诺的"简单、可定制、快速"的价值所在。