本节摘要:semgrep 与 CodeQL 不是新旧替代关系,而是轻与重的分工关系。本节先用「两张底片」的说法讲清两者的能力差异:semgrep 是词法与轻量数据流层面的快照,CodeQL 是完整语义关系的深曝光;再给出一张可决策的对比表,覆盖上手成本、规则形态、跨过程能力、建库代价与授权;然后按场景给分工建议——快筛天天跑、深挖定期跑,并把「规则沉淀」作为流水线的复利环节;接着用一个虚构但典型的注入案例走一遍双引擎协作全程;最后与《语义代码检索》划界:检索回答「代码在哪」,双引擎回答「缺陷模式是否出现」,两者在 AI 工作流里是上下游而非竞争者。
拍照的比喻最好用:同一个仓库,semgrep 拍的是「快照」——按规则逐文件扫一遍,几秒到几十秒出结果,看到的是代码表层形态加上污点模式补上的浅层数据流;CodeQL 拍的是「长曝光」——先把整个仓库抽成关系数据库,再让引擎在完整语义上做推导,跨函数、跨文件的数据流都能追,代价是建库要数分钟到数十分钟,且编译型语言需要真实构建环境。
没有哪张底片更好,只有哪张更适合当前问题:想知道「今天新写的代码里有没有明显坏味道」,快照够用;想知道「这个参数究竟能不能流进命令执行」,长曝光才是正解。把「轻快」与「深度」当成两个正交维度,后面的对比表就好读了。
# two_photos.txt —— 两张底片的直观对比 关注点 semgrep 快照 CodeQL 长曝光 ───────────── ───────────────────── ───────────────────── 一次扫描范围 规则 × 文本 查询 × 全库语义 跨函数追踪 浅(taint 模式有限) 深(引擎级污点) 首次等待 秒级 建库数分钟起 换仓库成本 零(直接扫) 重新建库
| 维度 | semgrep | CodeQL |
|---|---|---|
| 心智模型 | 模式即代码 | 代码当数据库 |
| 起步规则量 | 三行起步 | 数十行 QL 加谓词 |
| 首次出结果 | 分钟级(安装即扫) | 小时级(装 CLI、学 QL、建库) |
| 语言广度 | 多语言覆盖面广 | 主打主流语言,深度优先 |
| 跨过程数据流 | 污点模式支持,范围有限 | 引擎级完整污点追踪与路径输出 |
| 建库成本 | 无需建库 | 需要;编译型语言要真实构建 |
| CI 单次耗时 | 秒到分钟 | 分钟到小时(可增量维护数据库) |
| 规则生态 | 官方注册表海量即用 | 官方查询库加社区查询 |
| 授权 | 社区版免费 | 查询库 MIT;研究免费;商业 CI 需授权 |
(stars 与版本信息以官方仓库与文档为准,2026-10 社区口径分别约 17.1k 与 10.2k。)
表的使用方法是「先定约束再选工具」:预算里没有构建环境维护人力,长曝光再好也先放下;反过来,若结论要拿去支撑安全报告,只有带完整路径证据的 CodeQL 结果够格。
组合成流水线才是终态:
# pipeline.txt —— 双引擎流水线与规则沉淀 每次提交 ──▶ semgrep 快筛(分钟内反馈) 每周例行 ──▶ CodeQL 建库与污点查询(深度确认) 确认发现 ──▶ 证据链登记(第 11 章) │ ▼ 缺陷模式沉淀:写成 semgrep 规则或 QL 查询 ──▶ 下次自动拦截
最后一箭头常被忽视:人工确认过的缺陷模式,回头写成规则,下次自动拦截——静态分析的复利来自规则库的积累,而不是单次扫描的结果。
流水线的运转节奏也可以照抄:快筛挂在提交钩子上(开发者本地跑,秒级),深挖挂在每周定时的 CI 任务上(维护增量数据库,分钟级),规则沉淀放在每案结案时(第 11.3 节 SOP 的第十步)——三处各自异步,互不拖累。
双引擎各自的常见坑也要提前知道,避免把工具问题误当能力问题:
| 坑 | 现场 | 对策 |
|---|---|---|
| semgrep 规则过宽 | 一条 pattern 命中数百处,淹没告警 | 加 pattern-inside 限定上下文,或拆成多条窄规则 |
| semgrep 污点漏报 | 跨文件传播追不全 | 已知缺口改用 CodeQL 验证,规则只做快筛 |
| CodeQL 建库失败 | 编译型语言构建环境缺失 | 先保证本地能完整构建,再谈建库 |
| CodeQL 查询超时 | 全库污点无屏障谓词 | 加屏障收窄,或按目录分库 |
| 两引擎结论打架 | 一边报警一边静默 | 以 CodeQL 路径证据为准,回查 semgrep 规则是否误配 |
用一个典型场景把分工走活(项目为自建 Flask 演示应用,注入点为教学目的故意埋入):
os.system(拼接的用户输入)——message 提示可能的命令注入。os.system("ping -c 1 " + request.args.get("host"))——拼接明显,但要看 host 是否还有上游净化。往上翻两个函数没找到净化,初判「高度可疑」。四步里两台引擎各出现一次、人工出现两次——这个比例是健康比例:工具负责穷举与证据,人负责判断与处置。
演练的两个变体,顺手各做一遍:把 semgrep 规则收紧(加 pattern-inside 限定 handler 函数内),观察告警数下降但 CodeQL 结论不变——理解「快筛的查全与查准是可以调的」;再把 CodeQL 的 source 换成 JSON 请求体读取(6.2 节改写练习),观察同一 sink 的另一条入口链——理解「长曝光的广度来自查询改写而非重复扫描」。
本书读者常把这两类工具混为一谈,值得专门划界:
至于代码切分、嵌入索引这些检索侧的工程细节,本书不重讲,以《语义代码检索》第 03 章《代码切分》为准。一句话总括:检索是雷达,双引擎是探照灯——前者大范围找「哪里值得看」,后者对准一点问「到底有没有问题」。
职责对照落到操作层面:
| 环节 | 用检索 | 用双引擎 | 举例 |
|---|---|---|---|
| 冷启动陌生仓库 | 先检索建地图:入口在哪、核心模块叫什么 | 后置:对可疑模块快筛 | repo-map 定位上传处理模块 |
| 模式已经明确 | 不用 | 引擎验证 | 「输入流到 system 吗」一条污点查询 |
| 模式还在成形 | 检索找相似历史代码助你归纳 | 归纳成规则后再上引擎 | 历史注入案例归纳出本库的 sink 集合 |
| 日常巡检 | 不用 | 双引擎流水线跑规则库 | 每周例行的快筛加深挖 |
| 报告取证 | 不用 | CodeQL 路径级证据 | SARIF 附进证据链(第 11.1 节) |
双引擎就位后,记住三个接口:其一,扫描结果是第 10 章 AI 假设的验证器——模型说「这里可能有注入」,一条污点查询就能给出确定回答;其二,确认的模式沉淀为规则,是团队资产的积累形态;其三,SARIF 与 JSON 输出是机器可读的,可经 MCP 接进模型工具链,组织方式参考《OpenCodeReview》第 04 章《工具、MCP 与会话》。
还有一个伏笔:CodeQL 与 semgrep 的「代码模型」各自封闭——一个在数据库里,一个在规则文件里。如果你想要一张能自由遍历、源码与二进制通吃的图,那是下一章 Joern 与代码属性图的主场。
给管理者的一句话版本:两台引擎的预算叙事完全不同——semgrep 的成本几乎全在「人的规则编写时间」,CodeQL 的成本几乎全在「建库与维护的机器时间加学习时间」。前者按周见效,后者按季度见效,但后者的结论硬度(路径级证据)是前者给不了的;预算只够先上一个时,按你的报告读者是谁来决定:给开发者的及时反馈选前者,给安全评审的正式结论选后者。
引擎解决了「怎么查」,但查询语言仍是各家方言:semgrep 的 YAML、CodeQL 的 QL。有没有一种统一的表达,能把结构、流向、依赖放进同一张可自由遍历的图,让源码和二进制说同一种话?第 07 章《代码属性图Joern》给答案。