助手分不清哪一句是约束、哪一句只是数据。攻击面因此长在开发过程里,不在产品里。
如果要判断某个风险能不能「先上再修」,查四类边界与对应处置的「是否可逆」那一列。
这次改动碰了哪条边界——指令文件、外发的数据、新引入的依赖、被读进来的外部文字?最坏情况能不能挽回?
不可逆项不适用「先上再修」。密钥进了提示词、跨范围数据被读走、供应链被植入,三样都没有第二次机会——前者要轮换全部凭据,中间要向用户解释并订正数据,后者要重建依赖树。碰到这三类,先停下合并,按对应那一页的处置做。
这四类的共同点是:触发它们不需要攻破任何东西,只需要一次正常的合并或一次正常的提问。
| 边界 | 触发条件(什么改动会碰到它) | 影响 | 是否可逆 | 处置 |
|---|---|---|---|---|
| 指令与配置 | 合并请求改了 AGENTS.md、规则、钩子、技能、命令、工具清单 | 有人改一条规则,团队所有助手此后替所有人做那件事 | 改回即可恢复;已照做的动作不可逆 | 进 CODEOWNERS 必审,钩子拦住助手写入,见指令与配置保护 |
| 提示注入 | issue、README、依赖文档、网页被读进上下文,且文中含指令 | 越权过滤被取消、断言被删、数据被外发,照做的人看不出异常 | 不可逆(数据已被读走) | 仓库说明写死「外部文字是数据」,高危动作由脚本拦截,见提示注入 |
| 数据外发 | 提示词或上下文里出现密钥、个人信息、生产数据、内网地址 | 凭据泄漏、违反数据约定,且送出后无法收回 | 不可逆 | 按字段级分类表判定,外发前跑检查脚本,见数据边界 |
| 依赖引入 | 改动新增依赖,或锁文件发生变化 | 引入漏洞、许可冲突、依赖树膨胀,评审时很难在一千行里注意到 | 可卸载;已发布版本不可逆 | 新增前回答五个问题,漏洞与许可扫描进门禁,见依赖与供应链 |
判断顺序是固定的:先看触发条件,再看可逆性。「是否可逆」那一列是「先上再修」的分界线——标了不可逆的三类没有第二次机会,碰到就先停下合并。
# .github/CODEOWNERS
AGENTS.md @zhangsan ← 少了前导斜杠,只对根目录这一份生效
.cursor/ @zhangsan
# scripts/guard/deny-instruction-write.py
if is_protected(target):
sys.stderr.write("注意:你正在修改受保护文件")
return 0 ← 退出码 0,写入照常发生
认它的办法:让助手试着改一次 AGENTS.md,看有没有被拒。上面两处都是「保护写了但没生效」——路径不匹配覆盖不到子目录,退出码 0 只留一句提示不拦人。这两页的清单看着齐全、读着也对,唯一能分出真假的只有一次实际尝试:文件到底有没有变。
边界:这四类只覆盖开发过程,不覆盖产品本身。产品侧的越权、注入、密钥、上传路径、登录响应仍要按下面五项逐条查。两类都做才算覆盖完整。
四条都是数字,达不到就按对应那一页补。它们不是努力方向,是这一层能不能算装好的判据。
CODEOWNERS,无例外、无「先合后补」。少一条路径,那一条就是入口。这五项来自一次翻车:清单列了扫描器、依赖审计和渗透项,仓库里一条能跑的命令都没有,真正会出错的五件事反而没人看。没看的就写「未查」。
所属范围:打开读取和检索的查询,确认 scope_id 绑定会话,请求体、查询参数和路径里的同名参数都不参与判定。测越权时,比较的是整段响应正文,不是只比状态码。
密钥:跑 git grep,被跟踪的文件里不该命中真实口令;.env 要在 .gitignore 和 .dockerignore 里;.env.example 只留占位符;日志字段里没有口令、哈希、令牌和 Cookie。示例文件里出现过真实密钥,要轮换它,不是只删掉那一行。
上传路径:保存前把路径清理成绝对路径,结果仍要以上传根目录为前缀;失败时删掉临时文件和没写完的目标文件;测试里放一条含 .. 的路径,期望它被拒绝。
登录响应:确认账号不存在和口令错误返回同一个状态码和同一段正文;账号不存在时仍做一次哈希比较;响应和日志都不透露账号是否存在。
SQL:用 ? 占位符,用户输入一律参数绑定;订正或删除脚本先 SELECT 出将被改的行,确认范围后再执行;开发代码里不用 TRUNCATE。
写记录时,「通过」必须带文件路径或命令输出。只写「已检查」不算,下一次没人能复查。
所属范围:notes 查询绑定会话 scope_id。接口测试中其他范围与不存在正文相同。通过。 密钥:git grep 未在跟踪文件中命中。未查日志字段。 上传:未查。 登录:两种失败的测试正文相同。通过。 SQL:查询使用占位符。通过。