我让 AI Agent 在无人监管的情况下使用我的 Browser Tool,20 分钟内发现了 3 个漏洞。
I’m happy to translate the article for you, but I’ll need the actual text of the post. Could you please paste the content you’d like translated (excluding the source link you’ve already provided)? Once I have the article text, I’ll translate it into Simplified Chinese while preserving all formatting, markdown, and code blocks.
设置
- 被测应用: 本地运行的代码审查工具 Crit。
- 功能: 当点击行号栏打开评论表单时出现的 评论模板芯片。
- 验证场景: 浅色模式、深色模式、芯片插入、光标定位。
我已将 Charlotte 添加到项目的 MCP 配置中,并让代理测试模板功能。仅此——没有关于使用哪些工具或如何操作的指示。
成功之处
| Tool | Observation |
|---|---|
charlotte:navigate (with detail: "summary") | 为代理提供了页面的干净结构概览(标题、交互元素、内容块)。足以确认页面已加载并进行定位。 |
| Screenshots | 使用了两次(浅色模式、深色模式)。作为最终的“看起来对吗?”检查——快速、清晰,正是视觉验证工作流所需的。 |
| Tool‑profile switching | 代理在默认的 browse 配置文件上启动,意识到需要 JavaScript 求值,运行 charlotte:tools enable evaluate,随后继续。没有阻力。 |
Bug 1 – evaluate 静默吞掉多语句代码
代理需要查询 DOM 中的 gutter 元素,并编写了合理的 JavaScript:
var blocks = document.querySelectorAll('[data-line]');
var gutters = document.querySelectorAll('.gutter');
'dataLine=' + blocks.length + ' gutter=' + gutters.length;
Charlotte 返回 {value: null, type: "undefined"}——没有错误。经过多次尝试后,代理发现将代码包装在 IIFE 中即可工作:
(() => {
var blocks = document.querySelectorAll('[data-line]');
var gutters = document.querySelectorAll('.gutter');
return 'dataLine=' + blocks.length + ' gutter=' + gutters.length;
})()
根本原因
charlotte:evaluate 被实现为 new Function('return ' + expr)。JavaScript 的自动分号插入(ASI)把多行输入转换成了:
return; // ASI 在此处插入了分号
var blocks = …; // 死代码,永远不会被执行
于是函数静默返回了 undefined。随后一次单行尝试(return var g = …)产生了语法错误,导致了不同的失败模式。
修复方案
将 new Function('return ' + expr) 替换为 Chrome DevTools Protocol 的 Runtime.evaluate,该方法把代码当作程序执行,并返回最后一个表达式语句的完成值。Charlotte 已经维护 CDP 会话,因此可以直接替换,无需新增依赖。
Bug 2 – 无法在没有元素 ID 的情况下点击
代理在截图中看到了 gutter 行号并尝试使用坐标点击:
charlotte:click({ x: 38, y: 215 })
结果:Error: element_id is required.
gutter “ 元素没有 ARIA role,因此它们不会出现在可访问性树中,charlotte:find 也找不到它们。代理只能采用一种变通方法:
- 启用
evaluate。 - 编写内联 JavaScript 查询 DOM。
- 手动分发鼠标事件。
由于应用在 gutter 上通过 mousedown 开始拖拽选择,并在 document 上的 mouseup 监听器结束选择,代理必须精确模拟这一序列:
// 错误 – mouseup 目标错误
gutter.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
gutter.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
// 正确 – 与应用的监听器模式匹配
gutter.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
document.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
本应一次工具调用的操作,变成了十二步的反复试验。
修复
新增工具 charlotte:click_at({ x, y })。Charlotte 已经具备将元素 ID 转换为像素坐标并调用 Puppeteer 的 page.mouse.click 的管道。新工具只需跳过元素解析步骤,直接在 CDP 级别发送鼠标点击,从而产生自然冒泡的真实输入事件。
Bug 3 – find 看不到非语义元素
charlotte:find({ text: "Do the thing" }) // → []
charlotte:find({ type: "button", text: "1" }) // → []
渲染后的内容和行号虽然已经存在于 DOM 中,但并未在可访问性树中暴露。Charlotte 的 find 工具会过滤可访问性表示,这对于语义化的 UI 元素是合适的,但在自定义的、非语义化的部件(如 gutter)上就会失效。
修复方案(在演示中尚未完全实现)
- 当提供
semantic: false标志时,扩展find以可选地搜索原始 DOM 树。 - 或者公开一个新工具
charlotte:query_selector({ selector }),它返回任意 CSS 选择器对应的元素 ID,使代理能够定位并操作任意元素。
要点
- Charlotte 的结构化视图 非常适合导航和高层次验证。
- 截图 仍然是最简单的视觉检查。
- 工具的人体工学很重要: 静默失败(Bug 1)和过于严格的 API(Bug 2、Bug 3)会浪费宝贵的工具调用并减慢代理的速度。
- 迭代工具集——添加
click_at并改进evaluate——将有前景的原型转变为实用、低摩擦的 AI 代理自动化层。
实验证明,只要提供一个可靠的浏览器自动化原语,AI 代理就能快速发现真实的 bug。只要对 Charlotte 的 API 进行少量改进,同样的方法就可以扩展到更大、更复杂的 Web 应用。
修复
问题: Charlotte 的默认浏览模式依赖可访问性树。对于自定义 UI,任何没有语义角色的元素都是不可见的。
解决方案: 为 charlotte:find 添加 selector 参数,通过 CSS 选择器直接查询 DOM。
find({ selector: ".line-comment-gutter" })
- Charlotte 调用
DOM.querySelectorAll,提取基本信息(标签、文本、边界),并将每个匹配的元素注册到其 ID 系统中。 - 返回的 ID 可用于
click、hover、drag以及所有其他交互工具。 - ID 使用
dom-前缀,以便代理能够判断它们来源于 DOM 查询而非可访问性树。 - 语义观察模型保持不变;选择器模式是一个并行路径,生成兼容的元素 ID。
修复内容:Charlotte v0.4.1
这三项于同一天发布:
| 功能 | 更改 |
|---|---|
charlotte:evaluate | 直接使用 CDP Runtime.evaluate。多语句代码、var 声明、IIFE 以及单表达式都能自然运行。不再出现静默的 null。 |
charlotte:click_at | 接受 x/y 坐标并分发 CDP 级别的鼠标事件。支持左键、右键、双击以及修饰键。 |
CSS selector mode for charlotte:find | 接受 selector 参数,直接查询 DOM,返回带有 Charlotte ID 的元素,可供所有交互工具使用。 |
我学到的
使用 AI 工具进行自测需要 AI 自测
我已经使用 Charlotte 几十次,并阅读了它代码库的每一行。我永远不会手动发现 evaluate ASI bug,因为我本能地写 IIFE。代理没有这些本能;它会写出一个合理的开发者会写的代码,遇到障碍,然后准确地告诉你障碍所在。
十二步合为一步
click_at 的缺口把一次交互变成了一个涉及 DOM 查询、源代码阅读和手动事件分发的十二步变通方案。看到代理在一个本可以消除的变通方案上消耗代币,是优先处理待办事项的非常有效的方式。
可访问性树是必要但不足以进行测试
Charlotte 的结构化、语义化观察模型是浏览和审计的正确基础。但测试自定义 UI 意味着要与未语义化暴露的元素交互。find 上的 selector 参数在不影响默认体验的前提下弥合了这一差距。
- 浏览配置文件的用户 获得干净的语义化世界。
- 需要原始 DOM 访问的用户 可以直接触达。
关注原始会话,而不仅仅是结果
代理在结尾的写作报告很有用,但真正的信号在会话记录中:连续出现的三个静默 null,随后是缺失功能的十二步日益创意的变通方案,以及它从“我会点击这个”转变为“我需要在两个不同的 DOM 目标上分发合成鼠标事件”的确切序列。bug 就隐藏在那里。
试一试
npx @ticktockbent/charlotte@latest
- 进入全屏模式
- 退出全屏模式
Charlotte 是开源的,采用 MIT 许可证,并且可与任何 MCP 客户端配合使用:Claude Desktop、Claude Code、Cursor、Windsurf、Cline。