“上线前扫一遍安全”这件事,绝大多数中小团队都停留在口头。原因不复杂:商业 SAST 动辄按开发者人头收费,开源工具又分散在四五个仓库里,命令、配置格式、报告格式各不相同,光是把它们串起来就要耗掉一个人两周。于是安全扫描要么彻底不做,要么做成了 CI 里一个永远绿灯、没人看的步骤。
AI 编程助手的出现让这件事的门槛降了一大截——你可以用一句话让它写出 Semgrep 自定义规则、生成 Trivy 的 CI 配置、解释一条 CVE 到底影不影响你。但同样地,AI 在安全领域的幻觉代价远高于写业务代码:一条语法合法但永远匹配不到的规则、一个被编造出来的 CVE 编号、一句“这个漏洞已在 x.y.z 修复”的臆断,都会让你误以为自己安全了。
这篇文章横向对比 6 款完全免费(或对个人/开源项目免费)的代码安全扫描工具,给出每一款的真实命令、AI 提示词写法,并重点拆解 AI 在安全扫描场景最容易踩的 5 个坑——这些坑的共同特征是:命令能跑通、报告能生成、CI 显示通过,但实际上什么都没扫到。
一、先分清四层扫描,提问准确率立刻翻倍
大多数人向 AI 提问时说的是“帮我扫一下代码安全”,这句话太模糊,AI 只能猜。代码安全扫描其实是四种完全不同的技术,用的是不同的工具、不同的规则库、发现的是不同类型的问题。先把层级说清楚,AI 的输出质量会有质变。
| 层级 | 英文缩写 | 扫什么 | 典型问题 | 代表工具 |
|---|---|---|---|---|
| 静态代码分析 | SAST | 你自己写的源码逻辑 | SQL 注入、命令注入、路径穿越、硬编码算法弱点 | Semgrep、CodeQL、Bandit |
| 依赖成分分析 | SCA | lock 文件里的第三方库版本 | 已知 CVE、供应链投毒、许可证违规 | OSV-Scanner、Trivy |
| 密钥泄露检测 | Secret Scanning | 源码 + Git 历史提交 | AK/SK、Token、私钥、数据库密码 | gitleaks、Trivy |
| 基础设施配置检查 | IaC / Misconfig | Dockerfile、K8s YAML、Terraform | 特权容器、0.0.0.0 暴露、无资源限制 | Trivy、Checkov |
为什么这个表很重要?举个真实例子:有人问 AI“帮我检查代码里有没有 SQL 注入”,AI 给了一段 Trivy 命令。Trivy 是优秀工具,但它的默认强项是 SCA 和 IaC,对业务代码的注入类逻辑漏洞基本无能为力。命令跑完输出 “No vulnerabilities found”,提问者以为自己没问题,实际上扫描器压根就没往那个方向看。
提问模板:把层级写进第一句。「我要做的是 SAST 静态代码分析,语言是 Python,重点关注命令注入和反序列化,请用 Semgrep 实现」,比「帮我扫代码安全」的可用率高一个数量级。
二、6 款免费工具横向对比
| 工具 | 主战场 | 免费边界 | 上手难度 | 最适合的场景 |
|---|---|---|---|---|
| Semgrep | SAST + 自定义规则 | CLI 与社区规则库完全免费 | 低(规则语法接近源码本身) | 团队自定义规范落地、增量扫描 |
| Trivy | SCA + Secret + IaC + SBOM | 完全开源免费 | 极低(一条命令四种扫描) | 容器镜像与仓库的一站式体检 |
| gitleaks | 密钥泄露(含 Git 历史) | 完全开源免费 | 极低 | 提交前拦截、历史泄露排查 |
| OSV-Scanner | 依赖漏洞(Google OSV 库) | 完全开源免费 | 低 | 多语言 lock 文件统一扫描 |
| CodeQL | 深度数据流分析 | 公开仓库免费,私有仓库需 GHAS | 高(需学 QL 查询语言) | 开源项目深挖污点传播链 |
| Bandit | Python 专项 SAST | 完全开源免费 | 极低 | Python 项目快速基线检查 |
1. Semgrep:唯一值得让 AI 帮你写规则的工具
Semgrep 的规则语法最大的特点是「像写代码一样写规则」——你想匹配什么代码,就把那段代码写下来,把可变部分换成 $X 这样的元变量。这个特性让它成为最适合 AI 生成的安全规则语言。
semgrep scan --config auto .
semgrep scan --config p/owasp-top-ten --severity HIGH .
semgrep scan --config ./rules/ --sarif -o semgrep.sarif .
一条自定义规则的必填字段只有五个:id、message、severity、languages,以及 pattern / patterns / pattern-either / pattern-regex 四者中的任意一个。patterns 是逻辑 AND,pattern-either 是逻辑 OR,pattern-regex 走的是 PCRE2 多行模式。
rules:
- id: dangerous-os-system
message: 检测到 os.system 直接拼接变量,存在命令注入风险
severity: HIGH
languages: [python]
pattern-either:
- pattern: os.system(...)
- pattern: subprocess.call($CMD, shell=True)
2. Trivy:一条命令覆盖四种扫描
Trivy 的杀手锏是 --scanners 参数,可以在一次扫描里同时开启依赖漏洞、密钥、配置错误、许可证四类检查,输出统一在一份报告里。
trivy fs --scanners vuln,secret,misconfig .
trivy image --severity HIGH,CRITICAL --ignore-unfixed nginx:latest
trivy repo https://github.com/user/repo
trivy fs --format cyclonedx -o sbom.json .
--ignore-unfixed 是实战中最该加的一个参数:它会过滤掉“上游还没发布修复版本”的漏洞。这类漏洞你除了等没有任何办法,留在报告里只会淹没真正可修的条目。想给容器镜像本身做减法,可以配合 AI Dockerfile 生成工具的多阶段构建实践,基础镜像瘦一圈,CVE 数量常常能直接掉七成。
3. gitleaks:注意它的命令在近版本里换过一轮
gitleaks 专攻密钥泄露,最大的价值在于它能扫 Git 历史——底层调的是 git log -p,也就是说你把密钥从代码里删掉、提交了一次“移除敏感信息”,它照样能从历史 patch 里揪出来。
gitleaks git -v .
gitleaks dir --redact -f sarif -r gitleaks.sarif ./src
gitleaks git --baseline-path baseline.json .
三种扫描模式分别是 git(扫仓库历史)、dir(扫目录或文件)、stdin(从标准输入读)。配置文件的查找优先级依次是:--config/-c 指定 → 环境变量 GITLEAKS_CONFIG → 环境变量 GITLEAKS_CONFIG_TOML(直接放文件内容)→ 目标路径下的 .gitleaks.toml,四者都没有才用内置默认配置。
另外两个实用细节:--redact 支持传百分比(如 --redact=20 只遮蔽 20%),方便在日志里保留可辨识片段;代码里写 gitleaks:allow 注释可单点豁免,而 --ignore-gitleaks-allow 能强制忽略这些豁免做一次全量复查。
4. OSV-Scanner:Google 的依赖漏洞库,V2 换了命令结构
OSV-Scanner 的工作分两步:先从项目里提取依赖包清单,再拿去和 OSV 漏洞数据库匹配。它的覆盖生态很广,一个二进制搞定 npm / PyPI / Go / Maven / Cargo 等多种 lock 文件。
osv-scanner scan -r ./my-project-dir/
osv-scanner scan -L package-lock.json --format json --output-file result.json
osv-scanner scan image my-docker-img:latest
osv-scanner --licenses="MIT,Apache-2.0" path/to/repository
V2 版本把功能拆成了 scan source(默认,扫源码目录)、scan image(扫容器镜像)、fix(引导式修复)三个子命令。--serve 会把结果渲染成 HTML 并在本地 8000 端口起个服务,比翻终端表格舒服得多。离线环境可以用 --offline-vulnerabilities 配合 --download-offline-databases 预下载数据库。
需要特别提醒:osv-scanner fix 这个引导式修复命令在不受信任的项目上有风险——它可能触发包管理器执行项目里的脚本,或访问项目指定的外部 registry。给别人的仓库做安全审计时,只扫不修。
5. CodeQL:深度最强,但门槛也最高
CodeQL 把代码编译成一个可查询的数据库,然后用 QL 语言写查询去追踪污点从 source 到 sink 的完整传播路径。它能发现跨函数、跨文件的复杂漏洞,这是纯模式匹配类工具做不到的。代价是:需要能成功构建项目、查询语言学习曲线陡峭、扫描耗时以分钟计。公开仓库在 GitHub 上免费启用,私有仓库需要 GitHub Advanced Security。
codeql database create db --language=javascript
codeql database analyze db --format=sarif-latest --output=results.sarif
6. Bandit:Python 项目的三十秒基线
Bandit 只做 Python,规则数量不多,但胜在零配置、跑得飞快,适合当作提交前的第一道粗筛。
bandit -r . -ll -f json -o bandit.json
bandit -r ./app --skip B101,B601
-ll 表示只报告 MEDIUM 及以上级别。B101(assert 语句)在测试代码里满地都是,几乎是必须 skip 的一条。
三、AI 最容易踩的 5 个坑(命令能跑,但等于没扫)
下面这五个坑的共同点:AI 的输出语法完全合法,命令执行不报错,报告也正常生成,CI 甚至是绿的——但扫描的有效性已经归零。这也是它们比语法错误危险得多的原因。
坑 1:gitleaks 的 detect / protect 命令已被废弃
这是当前 AI 出错率最高的一个点。gitleaks 在 v8.19.0 引入了一次命令重构:detect 和 protect 被废弃,虽然为了兼容仍然可用,但已经从 --help 菜单里隐藏。取而代之的是 git、dir、stdin 三种模式。
问题在于,网上绝大多数教程和博客写的都是 gitleaks detect --source . -v,AI 的训练语料里这个写法占压倒性多数,于是几乎每次都会给你旧命令。它现在还能跑,但你写进 CI 里就等于埋了一颗定时炸弹,而且新版本的很多参数行为已经围绕新命令组织。
更值得注意的是,gitleaks 项目本身已经宣布进入 feature complete 状态——后续版本只做安全补丁,不再合入新功能,作者的精力转向了新项目 Betterleaks。这意味着如果你现在做技术选型,需要把「长期演进」这一条单独拿出来评估。这类判断永远不要交给 AI,去看仓库 README 的第一屏。
坑 2:Semgrep 规则的 severity 用了旧值,languages 字段被漏写
Semgrep 的 severity 取值已经更新为 LOW / MEDIUM / HIGH / CRITICAL。老的 ERROR / WARNING / INFO 分别对应 HIGH / MEDIUM / LOW,出于向后兼容仍然能用,但 AI 生成的规则里清一色是老值,这会导致你在按 --severity CRITICAL 过滤时,自己写的规则一条都筛不出来。
比 severity 更隐蔽的是 languages 字段。它是必填项,但如果 AI 填错了值(比如把 JavaScript 写成 javascript 之外的别名,或者给 .sh 文件写了错误的键值),Semgrep 不会报错,只会安静地不匹配任何文件。你看到的输出是 “0 findings”,看起来像是代码很干净,实际上规则根本没有被应用到任何文件上。
自检方法:写完规则后,故意造一个必然命中的样本文件跑一遍。如果连样本都匹配不到,问题一定出在
languages或 pattern 的缩进上,而不是你的代码真的很安全。
坑 3:AI 编造 CVE 编号和“已修复版本”
这是安全场景下 AI 幻觉危害最大的形态。当你把一份扫描报告贴给 AI,问它“这几个漏洞严重吗、升到哪个版本能修”,AI 有相当高的概率给出一个格式完全正确、看起来非常可信、但实际上并不存在的 CVE 编号,或者一个错误的修复版本号。
原因是 CVE 编号的格式极其规整(CVE-年份-数字),语言模型生成这种结构化字符串毫无压力,但它并没有实时的漏洞数据库。更麻烦的是,它对“这个漏洞在你的代码路径里是否真的可达”这一点,只能靠猜。
正确做法是把 AI 定位成「解释器」而不是「数据源」:漏洞编号、影响版本区间、修复版本,一律以 osv.dev 或 NVD 的原始条目为准;把原始条目粘给 AI,让它帮你翻译成人话、评估在你业务场景下的实际风险、给出升级的兼容性注意事项。这三件事 AI 做得非常好,前面那三件它做不了。
坑 4:OSV-Scanner 的 V1 / V2 命令混用
OSV-Scanner V2 引入了 scan source / scan image 的子命令结构,而 V1 时代的写法是直接 osv-scanner -r ./dir。AI 经常把两代语法拼在一起,产出类似 osv-scanner scan --lockfile=xxx -r 这样的组合。有些组合会直接报错(这反倒是好事),有些则会因为参数被忽略而只扫了一部分目录,输出一份“很干净”的报告。
判断依据很简单:跑 osv-scanner --version,V2 起才有 scan 子命令。写 CI 配置时把版本号锁死,别用 latest,否则某天上游发新版,你的流水线会以“扫描通过”的姿态静默失效。这个原则同样适用于 AI 生成的 CI/CD 流水线配置——所有第三方 Action 和二进制都应当锁版本。
坑 5:只扫工作区不扫历史,以及忘了设置 exit-code
两个动作缺一,安全扫描在 CI 里就是纯装饰。
第一,密钥扫描如果只扫当前工作区(gitleaks dir),那么“提交了密钥 → 发现后删掉 → 再提交一次”的经典场景完全检测不到,而这恰恰是密钥泄露最常见的形态。要扫历史必须用 gitleaks git。
第二,很多扫描工具默认即使发现问题也返回 exit code 0,CI 自然一路绿灯。gitleaks 的 --exit-code 默认值是 1(发现泄露即失败),但 Trivy 需要显式加 --exit-code 1,Semgrep 需要 --error。AI 生成 CI 配置时,这几个参数是最常被省略的。
trivy fs --scanners vuln,secret --severity HIGH,CRITICAL --exit-code 1 .
semgrep scan --config auto --error --severity HIGH .
gitleaks git -v --redact .
顺带一提,扫描失败后的告警要真的送到人手上才有意义,可以接到既有的 AI 监控告警配置体系里,别让它烂在流水线日志中。
四、约束式提示词模板
把下面这段作为系统提示词的一部分,AI 输出安全扫描配置的可用率会有明显提升。核心思路只有一条:宁可让它说“不确定”,也不要让它编。
你是资深应用安全工程师。请为我生成 [工具名] 的扫描配置,遵守以下硬性要求:
1. 只使用你确定存在的命令、子命令和参数;不确定的宁可不写,并单独列出「需要我确认的点」。
2. 明确声明你假设的工具主版本号(如 Trivy v0.5x / gitleaks v8.2x / OSV-Scanner v2)。
3. 不要输出任何具体的 CVE 编号、漏洞评分或「已在某版本修复」的结论。
4. CI 场景必须显式设置失败退出码,并说明该参数在此工具中的默认行为。
5. 密钥扫描必须区分「工作区扫描」与「Git 历史扫描」,并说明各自命令。
6. 自定义规则必须附带一个必然命中的最小样本,用于验证规则真的生效。
7. 报告格式统一输出 SARIF,便于多工具结果汇总。
8. 最后列出本次配置用到的全部命令与参数清单,供我逐条核对。
第 1 条和第 8 条是压制幻觉的关键组合:前者给了模型“承认不知道”的许可,后者强制它把所有主张摊开、便于你一眼扫过去发现异常。第 6 条则是针对 Semgrep 那类“静默不匹配”问题的专门防御。
五、四道校验流水线
AI 给出配置之后,别直接扔进 CI。按下面四步过一遍,成本大约十五分钟,能挡掉九成问题。
第一道:命令存在性验证
把 AI 给的每一条命令的 --help 跑一遍,确认子命令和参数真实存在。这一步专治坑 1 和坑 4。
gitleaks --help
osv-scanner scan --help
trivy fs --help
semgrep scan --help
第二道:阳性样本验证
这是最容易被跳过、却最有价值的一步。故意在临时目录里放一个必然触发的样本——一段明显的硬编码密钥、一行 os.system(user_input)、一个已知有漏洞的旧版本依赖——然后跑扫描。如果扫不出来,说明你的配置有问题,而不是代码很安全。
# 造一个必然命中的密钥样本
mkdir /tmp/sectest && cd /tmp/sectest && git init
echo 'AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' > config.env
git add . && git commit -m test
gitleaks git -v . # 必须报出 1 条 finding
第三道:SARIF 统一汇总 + 误报治理
六款工具六种报告格式,人工看会疯掉。全部输出 SARIF,用一个脚本合并,或直接上传到 GitHub Code Scanning 界面统一查看。
误报治理的原则是「有记录的忽略」而不是「关掉规则」:gitleaks 用 --baseline-path 建立基线,Trivy 用 .trivyignore,Semgrep 用 .semgrepignore 和 nosemgrep 注释。每一条忽略都应当带上原因和有效期,否则半年后没人知道为什么这条被关了。
第四道:人工五项清单
- 扫描范围是否覆盖了 Git 历史,而不只是当前工作区?
- CI 是否会因为发现高危问题而真的失败(退出码非 0)?
- 所有工具版本是否锁定,而非使用 latest?
- 报告是否有人真的会看,还是只存在于流水线日志里?
- 忽略清单里的每一条,是否都能说清为什么忽略?
六、三个真实踩坑案例
案例一:绿灯跑了三个月的密钥扫描
某团队在 CI 里加了 gitleaks,一直是绿的,大家很满意。直到一次外部安全评估,从仓库历史里翻出了七个月前提交的一组云厂商 AK/SK。复盘发现,CI 里用的是 gitleaks dir .——只扫工作区。那组密钥在提交后第二天就被从代码里删掉了,但历史 patch 里完好无损地躺着。
教训:dir 适合扫非 Git 目录或构建产物;只要目标是 Git 仓库,就该用 git 模式。两者可以都跑,但 git 不能少。
案例二:AI 编造的 CVE 让团队白升了一次级
某项目扫出十几个依赖漏洞,负责人把报告贴给 AI 让它排优先级。AI 输出了一份非常专业的分级表,其中标红的一条附带了具体 CVE 编号和“升级到 4.2.1 即可修复”的结论。团队照做,升级引发了两个接口的行为变更,回归测试花了一整天。事后核对 osv.dev,那个 CVE 编号根本不存在,真实的修复版本也不是 4.2.1。
教训:让 AI 排优先级可以,但它给出的任何编号和版本号都必须回到原始漏洞库核对。判断线上影响面时,配合 AI 日志分析工具看真实调用路径,比听 AI 猜靠谱得多。
案例三:Semgrep 自定义规则半年没匹配到任何东西
某团队让 AI 写了一批内部编码规范的 Semgrep 规则,跑起来一直 0 findings,大家以为规范执行得很好。半年后有人手动检查,发现违规代码遍地都是。排查下来,问题出在规则文件里 languages 写成了 [js]——这不是 Semgrep 接受的键值,规则被静默跳过,既不报错也不匹配。
教训:任何新增规则上线前,必须用阳性样本验证一次。「0 findings」在安全领域从来不是好消息,它首先应该被当作「配置可能有问题」的信号。
七、组合技:不要让 AI 从零发明规则
这是全文最实用的一条建议:不要让 AI 凭空创作安全规则,让它做「改写」而不是「创作」。
Semgrep 官方 Registry 有数千条经过生产验证的规则,覆盖 OWASP Top 10、各主流框架的常见误用。正确的工作流是:
- 先用
semgrep scan --config p/owasp-top-ten .跑一遍官方规则集,看看能命中什么。 - 找到最接近你需求的那条官方规则,把它的 YAML 完整贴给 AI。
- 让 AI 在这条规则的基础上做修改——换函数名、加一个
pattern-not排除白名单、调整 severity。 - 用阳性样本验证改写后的规则确实生效。
这样做的幻觉率比让 AI 从零写规则低一个数量级,因为模板的骨架是真实的,AI 只需要做局部替换。同样的思路适用于其他配置类工作——Kubernetes YAML、Nginx 反向代理配置都遵循这个规律:给 AI 一个已验证的模板做改写,永远比让它凭空生成更可靠。
八、五类高频误报及处理方式
扫描器接进来的第一周,报告里通常有七成是噪音。如果不做治理,团队很快就会形成「反正都是误报」的心理,扫描就此名存实亡。下面五类是出现频率最高的,处理方式也最固定。
| 误报类型 | 典型表现 | 推荐处理 |
|---|---|---|
| 测试代码里的假密钥 | 单元测试的 mock token、示例 AK | 加入 baseline 或 gitleaks:allow 注释,不要整目录排除 |
| 上游未修复的依赖漏洞 | 报出 CVE 但没有可升级版本 | Trivy 加 --ignore-unfixed,单独建一份跟踪清单 |
| 不可达的依赖漏洞 | 漏洞在你从未调用的函数里 | 记录忽略原因与复查日期,不要永久关闭 |
| Bandit 的 assert 告警 | B101 在测试文件里刷屏 | --skip B101,但仅对测试目录生效 |
| 构建产物被重复扫描 | node_modules、dist 里的第三方代码 | 用 ignore 文件排除产物目录,而非降低 severity |
治理误报有一条铁律:永远忽略「具体的某一条 finding」,而不是「整条规则」或「整个目录」。前者的影响面是可控的,后者会在你毫无察觉的情况下把未来的真问题一起吞掉。gitleaks 的 --baseline-path 就是为此设计的——它记录的是当前已知的具体条目,新出现的泄露依然会报。
另一条同样重要:给每条忽略写上「为什么」和「什么时候复查」。半年后接手的人看到一条没有注释的 ignore,只有两个选择——盲目相信,或者花半天重新分析。两个选择都很糟糕。
九、什么时候该上,什么时候不必
不是所有项目都需要六款工具全上。按项目形态做个简单的决策:
- 个人项目 / 内部工具:只上 gitleaks 一款,接成 pre-commit hook。密钥泄露是唯一会造成实质损失的风险,其他的可以缓。
- 对外提供 API 的服务:gitleaks + OSV-Scanner + Semgrep 三件套接进 PR 检查。注入类漏洞和依赖 CVE 是主要攻击面。
- 容器化部署的生产服务:在上面基础上加 Trivy 的镜像与 IaC 扫描,覆盖基础镜像漏洞和配置错误。
- 开源项目:直接启用 GitHub 的 CodeQL(公开仓库免费),深度最高且零维护成本。
- 还没有 CI 的项目:先别想扫描,把流水线搭起来是更优先的事。
过早引入过多工具的典型后果是:报告没人看、误报没人治、CI 时间从两分钟涨到十五分钟,最后被整体注释掉。安全工具的价值不在于装了几个,而在于是否真的有人在响应它的输出。
十、一套可以直接抄的最小配置
如果你只想花二十分钟把安全扫描接进项目,用下面这套:三个工具,覆盖 SAST、SCA、Secret 三层,全部免费。
# 1. 依赖漏洞(每次 PR)
osv-scanner scan -r . --format sarif --output-file osv.sarif
# 2. 密钥泄露(含历史,每次 PR)
gitleaks git -v --redact -f sarif -r gitleaks.sarif .
# 3. 静态代码分析(每次 PR,只报高危)
semgrep scan --config auto --severity HIGH --error --sarif -o semgrep.sarif .
# 4. 每周一次全量深扫(含 IaC 和许可证)
trivy fs --scanners vuln,secret,misconfig,license --severity HIGH,CRITICAL .
PR 阶段只跑前三条,控制在两三分钟内,不影响开发节奏;全量深扫放到定时任务里,结果单独送到安全负责人手上。命令行不熟悉的话,可以配合 AI 命令行助手把这些命令解释清楚再落地。
结语
AI 让安全扫描从「需要专职安全工程师」变成了「一个后端也能搭起来」,但它同时也制造了一种新的风险:看起来在扫,实际上没扫。语法错误会立刻暴露,而配置失效是沉默的——它会一直给你绿灯,直到出事那天。
所以这篇文章反复强调的其实只有一件事:用阳性样本验证你的扫描器真的在工作。造一个必然命中的样本,跑一遍,看它报不报。这个动作只要三十秒,却是区分「真安全」和「假安全」的唯一可靠方法。工具会更新、命令会废弃、AI 会幻觉,但阳性样本永远不会骗你。
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论