本节摘要:semgrep 是轻量多语言静态分析工具,口号是「模式即代码」——规则本体就是 YAML 里的一段代码模式。本节从安装和第一场自动扫描开始;然后用官方宣传的「三行规则起步」写出第一条自定义规则;接着过一遍规则语法要点:组合匹配、元变量捕获、污点模式三件套;再拿一个自建的小型 Flask 项目跑官方规则集,学会读结果而不是只数告警数;最后澄清 semgrep 的 AI 能力官方口径——误报分诊宣称降低约八成人工工作量、AI 修复建议与 MCP 支持,并强调「口径以官方文档为准」。研究对象全部使用自建项目与开源代码。
semgrep 社区热度约 17.1k stars(2026-10 社区口径),社区版免费,安装一条命令:
# install_semgrep.sh —— semgrep 安装与版本确认 pip install semgrep semgrep --version
第一场扫描不需要写任何规则,auto 模式会按项目语言自动挑选官方规则:
# first_scan.sh —— 自动规则首扫:进入项目根目录执行 cd myflask semgrep scan --config auto --metrics=off .
--metrics=off 关闭匿名使用上报,在敏感研究环境里建议常带。扫描输出按文件分组列出规则编号、严重级别、命中的代码行。此时你大概率会看到几条低置信的 NOTE——先别急着重写代码,6.1 节末尾的分诊方法与第 11 章的证据链会告诉你怎么处理它们。
提示:官方文档与规则注册表(registry)是唯一的权威口径来源,本节所有命令参数以 semgrep 官方文档为准。
官方宣传「规则可以从三行写起」。最精简的规则核心确实只有三件事:匹配什么(pattern)、说什么(message)、适用什么语言(languages),再加上 id 与 severity 两个登记项。先造一个测试对象——自己写的表达式小工具:
# calc.py —— 自建测试对象:表达式计算小工具(仅用于测试规则) def run(expr): return eval(expr) def safe_run(expr): return eval(compile(expr, "<input>", "eval"))
三行起步的规则:
# no_eval.yml —— semgrep 最小规则:报告 eval 调用 rules: - id: python-eval-used pattern: eval(...) message: 检测到 eval 调用,确认参数来源是否可控 languages: [python] severity: WARNING
运行与读结果:
# run_rule.sh —— 用自定义规则扫描测试文件 semgrep scan --config no_eval.yml --metrics=off calc.py
输出会把 run 与 safe_run 里的两处 eval 都标出来。这条规则已经具备生产能力的三要素:可版本管理(规则是文件)、可解释(message 就是文档)、可复用(换一个仓库照样跑)。所谓「模式即代码」,是说模式直接用目标语言写,不用学新的匹配 DSL。
规则语法常用件可以归成一张表:
| 语法件 | 作用 | 一句话例子 |
|---|---|---|
| pattern | 单一模式 | eval(...) |
| patterns | 与组合 | 同时满足多个条件 |
| pattern-either | 或组合 | 多个危险写法一网打尽 |
| pattern-not | 排除 | 排除已知安全的等价写法 |
| metavariable | 捕获与复用 | 把 $X 抓出来参与后续判断 |
| pattern-inside | 上下文限定 | 只看函数体内、只看循环内 |
| mode: taint | 污点模式 | 从 source 到 sink 的数据流 |
元变量示例——把「谁赋值给谁」抓出来:
# self_assign.yml —— 元变量捕获:报告变量自赋值 rules: - id: self-assignment patterns: - pattern: "$X = $X" message: 变量 $X 被赋值给自身,疑似笔误 languages: [python] severity: ERROR
污点模式是规则语法的进阶形态。它不再问「这一行长什么样」,而是问「脏数据能不能流到这里」。三件套如下:
# open_taint.yml —— semgrep 污点模式:request 参数流向 open rules: - id: flask-open-taint mode: taint languages: [python] severity: ERROR message: 用户可控参数未净化即进入 open,可能路径穿越 pattern-sources: - pattern: request.args.get(...) pattern-sinks: - pattern: open(...) pattern-sanitizers: - pattern: secure_filename(...)
在自建 Flask 项目上试跑:只要 request.args.get 的返回值经过任何赋值、拼接、传参到达 open,且中途没过 secure_filename,就会报 ERROR。把净化调用补上,告警消失——这条「补净化消警」的正反馈,正是污点模式教学价值所在。
官方规则注册表用 p/ 前缀引用,常用包:
# registry_scan.sh —— 对自建项目与开源副本跑官方规则集 semgrep scan --config p/owasp-top-ten --metrics=off ./myflask semgrep scan --config p/secrets --metrics=off ./myflask semgrep scan --config p/default --json --out=semgrep.json --metrics=off ./myflask
对开源项目(选一个许可证允许的仓库副本)或自己写的练手项目,按三步读结果:
若在 CI 中使用,可以加 --error 让命中高级别规则时以非零码退出,形成门禁;研究场景则建议 --json 落盘,交给后续的分诊与证据链流程(第 11 章)。
semgrep 平台侧的 AI 能力有三块,引用时务必区分「官方宣称」与「独立验证」:
分诊的定位要摆正:AI 能把「明显误报」压下去,让人的注意力集中在高价值告警上;但它不产出最终结论。结论永远来自证据链——第 11.1 节的四列模板会接住这里输出的每一条高置信告警。
注意:本节扫描对象限于自建项目与许可证允许的开源代码;对他人的闭源或在线系统进行未授权扫描不在本书范围内。
模式匹配跑得快,但它看不见「数据跨过三个函数之后去了哪里」,也很难回答「同一类缺陷在仓库里还有多少变体」。这两个问题正是下一节 CodeQL 的主场——先把代码变成数据库,再用一条查询把整条污点路径捞出来。