bughunt 是做什么的?
单元测试和大多数 UI 测试只检查它们的作者想到的代码路径,通常通过跳过真实点击和按键的测试钩子。用户不会那样使用应用。他们会点击钩子未覆盖的地方,在做完任何操作后都按撤销,在狭窄的窗口里工作,中途断网,并打开同事发来的奇怪文件。他们遇到的绝大多数漏洞早已存在,而没有任何检测在观察。
这个技能会去观察。它用真实的输入驱动真实的应用,一次一个区域,并将每个已确认的漏洞记录两次:一次作为留在仓库中的失败测试,这样漏洞就无法悄无声息地再次出现;一次作为仓库漏洞列表中的一个条目,以便有人修复它。一次狩猎不会修复产品代码。它只是发现、证明并记录下来。
模式
- Hunt(狩猎)(默认模式):选择一个区域,进行狩猎,记录发现,更新轮询。每次运行只处理一个区域,以保证狩猎足够深入,能发现真正的漏洞。
- Set up(设置):当仓库没有任何驱动应用端到端的方法,或缺少
references/boundaries.md中的边界时,使用此模式。构建最小的演练路径,使其能在每个界面上运行一个功能(references/walkthroughs.md),然后进行狩猎。 - Sweep(扫描):运行所有现有的演练,并报告自上次运行以来新增、更改或修复的内容。扫描可以发现回归,而不是新型漏洞;它与狩猎配合使用。
- Rules(规则):向路径添加
references/invariants.md中的一条规则,使其在每个演练的每一步都运行。一条规则(例如“撤销会精确恢复之前的状态”)就能在可能没人会编写脚本测试的功能中发现十几个漏洞。
1. 首先了解基本规则
阅读仓库的说明文件(CLAUDE.md、AGENTS.md、CONTRIBUTING、任何规则或交接记录)、运行应用的 README 以及现有的端到端测试。找出:
- 如何启动应用拥有的每个界面:开发服务器、桌面构建、移动模拟器。一个界面是指一个宿主(Web、桌面、移动端)在一种外观(浅色、深色、高对比度)和一种尺寸(宽屏、窄屏、手机)下的组合。
- 漏洞在哪里被追踪(漏洞文件、待办事项列表、问题追踪器)以及那里使用的条目格式。使用该格式进行编写。
- 测试绝不能触及的内容:真实账户、真实用户文件、生产数据、真实电子邮件、支付、用户的剪贴板、打印机和浏览器、已安装的应用副本。阅读
references/boundaries.md,并在驱动任何内容之前应用所有适用的边界。这是狩猎中可能造成实际损害的部分,因此要首先处理。 - 仓库是否禁止任何其他操作(推送、网络访问、付费服务、启动代理)。这些规则优先于本技能。
只有在答案会改变你可触及的内容时才提问。否则,自行选择,并将假设写入狩猎报告中。
2. 选择区域并列出其功能
使用用户指定的区域。否则,读取 bughunt/areas.md(首次运行时从 templates/areas.md 创建),并按此顺序选择:从未狩猎过的区域,然后是其路径自上次狩猎以来有变更的区域(在其路径上运行 git log),最后是最久未狩猎的区域。一个区域是一组用户一起使用的功能:文档编辑器、文件浏览器、登录和账户、设置、分享、支付。
从仓库中已有的信息列出该区域的功能:功能注册表、菜单和命令列表、路由、帮助页面、代码的入口点。每个功能必须在每个界面上至少执行一轮测试。
3. 狩猎
对于每个功能,首先像用户一样从头到尾操作一遍,使用真实的合成文件。然后,按照 references/checklist.md 进行操作。它是一份“敌对用户操作清单”,反复导致应用崩溃:每一步后都撤销、仅键盘操作、窄窗口、暗模式、中途断网、最大和最坏的文件、通过另一种格式进行往返、在两个标签页中操作相同内容、故意制造失败。
使用真实的输入事件(点击、按键、拖拽、打字)以人的节奏驱动应用。脚本的运行速度快于人,因此在一次点击后应留出短暂的停顿,然后在两个你期望分别撤销的更改之间,留出半秒的间隔。否则,你会把时序问题误报为漏洞。
在每一步中观察:
- 错误的结果,或者一个正确结果却没有保持住(在重新加载、保存、撤销或切换标签页后消失)
- 工作丢失:编辑内容、设置、选择、滚动位置
- 撤销操作回退过多、过少或没有反应
- 命令没有任何效果,仅从某个入口点有效,或者在无法运行时未被禁用
- 原始错误文本:堆栈跟踪、错误代码、内部路径
- 控制台错误、崩溃、空白窗口、卡死
- 文本过淡、被裁剪、重叠或单词中间断开(
references/invariants.md中的截图检查会自行捕获大部分此类问题) - 在另一个界面上相同步骤行为不同
在看到每个候选问题时立即记录下来,包含界面、步骤和你的预期。然后继续操作;在功能测试结束时进行确认。
4. 在提交前确认
一次狩猎的价值取决于其发现的真实性。对于每个候选问题:
- 从干净状态复现两次,以人的节奏,手动或通过最小脚本。如果无法复现,则放弃它,或者以“未验证:”的标签记录你看到的内容和位置。
- 排除测试假阳性。 时序(见上文)、匹配了错误元素的选择器、测试自身的存根、未聚焦的窗口、指针悬浮在某个有悬停状态的元素上。一个随着指针位置出现或消失的发现是真实的,但需要在其测试中固定指针位置。
- 在代价低廉时找出原因。 在可能的位置阅读代码,并命名文件和函数。不要在此花费太长时间;修复者会处理。
- 检查追踪器。 如果已有条目覆盖了它,则将新的界面或细节添加到该条目,而不是创建新条目。
- 按原因分组。 多个症状通常共享一个原因(一个缺失的键绑定可能会破坏一个宿主上五个编辑器中的撤销功能)。要明确指出,这样修复就只需一次。
5. 记录每个已确认的漏洞
- 首先是失败的测试。 在功能的演练(或新的演练)中添加一步,其预期说明应该发生什么。运行它并检查它是否失败,并且失败是因为漏洞的原因,而不是其他原因。一旦漏洞被修复,它就会通过。绝不要为了通过运行而削弱现有的预期或截图检查。如果某个检查有误,在报告中说明原因,并将其留给人工处理。
- 然后是漏洞条目,放在仓库漏洞列表的末尾,使用其格式,并遵循
templates/bug-entry.md:用用户的语言描述症状、实际行为与预期行为的对比、测量到的数值、代码中的位置、哪个测试保护了它、以及它在哪些界面上出现。 - 狩猎中不修改产品代码,除非用户要求修复。边发现边修复会掩盖发现,并将两种审查混在一起。
- 如果仓库规则允许,提交新测试和漏洞条目,并按名称暂存文件。除非用户指示,否则不要推送。
6. 报告和轮询
根据 templates/hunt-report.md 编写狩猎报告:区域、界面、每个发现及其测试和条目、你放弃的候选问题及原因、你无法测试的内容、以及按原因分组的建议修复方案。更新 bughunt/areas.md 中该区域的行(日期、提交、发现结果)。
最后,用通俗的语言告知用户:确认了多少个漏洞、最严重的三个、修复分组、以及轮询会选中的下一个区域。
持续发现漏洞
一次完成的狩猎留下的测试可以捕获相同漏洞的再次出现。新的漏洞需要新的观察:
- 按轮询再次狩猎。在区域发生重大变更后重新狩猎。
- 将经常在新位置出现的漏洞转化为一条规则(
references/invariants.md),以便每一步都检查它。 - 用户报告的每个漏洞,在修复之前都要变成一个演练步骤。
- 在每个版本发布前运行整个路径,如果仓库允许调度器,则每晚运行。只报告新的和变更的失败,以免已知漏洞掩盖新漏洞。
当工作量很大时
如果你可以将工作拆分为多个模型或会话,请使用你最强的模型进行狩猎,并使用不同的模型在新会话中进行确认。第二轮会从干净状态重新运行每个失败的测试,并从头阅读每个条目。这能捕获单轮测试中容易保留的、自信的假阳性。