结果先说:
自动扫描:100 条
人工确认:没有一条能直接作为真实漏洞交付
人工复核:另外发现 2 个 P1 和 4 类中低风险
测试对象是一个真实的三节点 Go KV 项目,不是故意写坏的示例仓库。
扫描命令:
code-audit . \
--format json \
--output runproof-kv-scan.json \
--with-metadata \
--ignore "data/**" \
--ignore "dist/**" \
--ignore "docs/**" \
--ignore "**/*_test.go"
扫描摘要:
| 规则 | 数量 |
|---|---|
ruby-rce | 79 |
sql-concat | 13 |
unvalidated-file-path | 8 |
| 合计 | 100 |
如果从第 1 条开始修,大概率会把 100 条全部当真实漏洞。
我处理这批结果时只做了 4 步。
第 1 步:先按规则和文件分组
不要先看严重度,也先不要把结果交给别人。
先按两个字段分组:
pattern + file
最后得到的是三组结果,而不是 100 个独立问题。再抽每组第一条看代码,判断这批结果是不是同一个原因造成的。
这一步的目标只有一个:把“100 条告警”压缩成“几个需要确认的原因”。
第 2 步:先看语法是不是属于该规则的语言
这个 Go 项目的扫描结果里,最多的 ruby-rce 并不是 Ruby 代码。
原因是扫描规则把以下写法误认定为 Ruby 命令替换:
- Go 反引号原始字符串;
- JavaScript 模板字符串;
- 普通代码片段里的反引号。
sql-concat 里也出现了类似问题:
Delete
deleteHandler
规则看到了类似 SQL 关键词的文本,但代码并没有把用户输入拼进 SQL。
unvalidated-file-path 则把 path/filepath 导入和路由参数当作文件读取。
第一轮确认可以先回答一句话:
这段语法真的是当前规则面对的语言和执行方式吗?
答案是否定时,先标成“语言或语法误报”,不要继续按真实漏洞排期。
第 3 步:真正可疑的结果,追到执行入口
误报不代表全部结果都能跳过。
对剩下的结果,只追三件事:
- 输入是否来自用户、文件、网络或外部系统;
- 输入能不能到达危险执行入口;
- 中间是否存在参数化、转义、校验或 allowlist。
例如一条 sql-concat,如果最后走到:
request -> sql string -> db.execute
才需要按真实问题处理。
如果路径是:
函数名包含 delete -> 规则匹配
那它只是一次文本命中。
这一步完成后,给每条结果只留一个结论:
真实可达
需要上下文
明确误报
扫描器看不到,人工补
不要写“看起来像”“应该没事”这种无法复查的备注。
第 4 步:把人工发现和自动结果放在一起
自动扫描没有发现的,不代表项目没有问题。
同一轮人工审查另外发现了:
- P1:未认证请求可以轮换
X-API-Key绕过限流; - P1:内部监听地址没有执行和公网监听一致的 TLS/loopback 检查;
- P2:Read Key 可以导出全量 KV,并看到内部 Raft 地址;
- P2:备份没有格式版本、校验和和恢复前完整性校验;
- P2:Docker Compose 默认允许不安全模式和明文 HTTP;
- P3:HTTP API 层缺少认证、限流和恢复接口测试。
这些问题扫描器没有直接报出来。
所以最后的交付不是:
扫描器报了 100 条
而是:
100 条自动结果完成分类;
其中没有一条可以直接交付为真实漏洞;
人工复核另有 2 个 P1、4 类中低风险。
我会留一张什么样的表
给团队或客户的结果表,不需要先贴 100 行原始 JSON。
先给这张表:
| 规则 | 文件或模块 | 输入来源 | 是否可达 | 结论 | 下一步 |
|---|---|---|---|---|---|
ruby-rce | Go/JS 字符串 | 无 | 否 | 语言误报 | 收敛规则 |
sql-concat | 函数名匹配 | 无 | 否 | 文本误报 | 增加反例测试 |
| 路径规则 | 导入或路由 | 无 | 否 | 上下文误报 | 调整规则范围 |
| 限流绕过 | HTTP 入口 | 外部请求 | 是 | 真实 P1 | 限流键加入真实 IP |
| 内部明文监听 | 节点启动 | 网络 | 是 | 真实 P1 | 增加 loopback/TLS 强制检查 |
原始 JSON 仍然保留,用于复查和回归测试,不把原始结果当作最终报告。
三个不要
第一,不要因为一批误报就把整类规则关闭。先看规则是不是缺少语言隔离和反例测试。
第二,不要把 100 条结果全部塞进 baseline 后不再处理。baseline 应该放已经人工确认的历史问题,而不是代替确认。
第三,不要把 0 findings 当成安全结论。扫描器没有匹配到规则,只能说明当前规则和样本没有发现问题。
这段流程最省时间的地方
最省时间的不是立刻修代码,而是先把自动结果和人工结论分成两层。
自动结果负责提示位置,人工确认负责判断可达性和实际影响。只有确认后的结果,才适合进入修复排期、交付报告和回归测试。
扫描器给的是线索,报告给的是判断。
公开规则、示例报告和命令说明:
需要本地扫描器完整源码版: