0 findings 可能是最危险的绿灯:我补了一份修复契约

19 阅读2分钟

扫描器从 1 条 High 变成 0 条,你会怎么做?

以前的我会直接截图,写一句“修复完成”。

现在我会先问:

原来的风险路径,真的被证明消失了吗?

因为扫描清零只能证明当前规则没有再次匹配。

它不能证明密码哈希、账号枚举、限流、日志、会话和权限都已经安全。

cover-掘金-修复契约-20260915.png

两份代码,四个测试全绿

一份使用参数化查询:

db.execute(
    "SELECT id FROM users WHERE username = ? AND password = ?",
    (username, password),
)

另一份直接把用户名和密码拼进 SQL:

sql = (
    "SELECT id FROM users "
    f"WHERE username = '{username}' AND password = '{password}'"
)
db.execute(sql)

有效密码返回 200,无效密码返回 401。

行为测试看结果,两份代码全部通过。

如果只看这里,拼接 SQL 没有任何问题。

真正的修复契约

我加了一条执行边界测试:

assert "?" in sql
assert db.params == (username, password)
assert username not in sql
assert password not in sql

结果:

unsafe_login: FAIL
params=None

safe_login: PASS
params=('alice', 'correct-password')

关键不在这条测试多复杂,而在顺序:

旧实现能够复现失败
新实现能够通过同一份契约
目标规则扫描清零
剩余风险单独列出

如果旧实现也通过,你根本不知道测试是不是有效。

如果新实现没有通过,扫描清零也只能说明规则没匹配,不能说明行为边界已经改变。

为什么绿色结果更危险?

红色告警会迫使团队处理。

绿色结果会让团队停止思考。

尤其是安全扫描:

规则没匹配
不等于
风险不存在

一个干净的 0 findings 更准确的解释是:

在本次规则和扫描范围内
没有发现已知危险模式

它不是:

代码已经安全

现在我会在报告里写什么

风险路径:
用户名和密码直接进入 SQL 字符串

修复证据:
旧实现契约失败
新实现契约通过

扫描证据:
目标规则从 1 条 High 降为 0

剩余风险:
密码哈希、账号枚举、限流、日志、会话、权限未覆盖

这样团队讨论的就不再是“扫描器为什么没有报”。

而是:

我们到底证明了什么?
还剩什么没有证明?

你对“0 findings”会写安全结论吗?

公开规则和示例报告:

github.com/yuan1521913…