他离开电脑几分钟,回来看见 AI 在自己开浏览器

小岛AI 2026 / 06 / 12

事情是这样的。

Simon Willison 昨天发了篇博客,标题就五个词,Claude Fable is relentlessly proactive,翻成中文大概是,这 AI 主动得不依不饶。

不认识 Simon 的朋友我一句话带过,Django 框架的联合创造者,开源数据工具 Datasette 的作者,写了二十年技术博客,是整个英文圈测评大模型最勤快也最较真的人之一。这种人说一个模型「主动得吓人」,分量跟营销号喊「炸裂」完全是两码事。

他的故事我看完愣了好一会儿,然后决定今天就跟你唠这个。

故事的起点小得不能再小。Simon 在折腾他的 Datasette Agent,发现跳转菜单的聊天输入框底下多了一条不该有的水平滚动条。就是那种你我每周都会遇到的 UI 小毛病,CSS 哪里溢出了一点,textarea 底边鼓出一条灰色滚动条,丑,但不影响功能。

他截了张图,在项目目录里开了个新的 Claude Code 会话,把截图拖进去,敲了一行字,大意是,看看依赖,帮我搞清楚这里为什么有条水平滚动条。

然后他被家务事叫走了。

注意,到这里为止,他给 AI 的全部输入是,一张截图,加一句话。没有配浏览器自动化工具,没有装任何插件,没有额外授权。就是一个跑在终端里的命令行 agent。

几分钟后他回到电脑前,看见他的机器自己打开了一个 Firefox 窗口。不是什么无头浏览器,是他日常在用的那个 Firefox,而且窗口正在自己导航到出 bug 的那个对话框。

他的第一反应跟你我一样,???

他很确定 Claude Code 没有操作鼠标键盘的能力,也确定自己没给它任何浏览器工具。那这玩意是怎么把浏览器打开的,又是怎么「看见」页面的。

他没急着掐掉,搬了个小板凳看它继续表演。接下来的几分钟,是我今年读到的最精彩的 agent 行为观察记录。

第一招先来。Simon 在终端里抓到这么一条命令,AI 正在跑

open -a Safari /tmp/textarea-scrollbar-test.html && sleep 4 && \
uv run --with pyobjc-framework-Quartz python - <<'EOF'
import Quartz
wins = Quartz.CGWindowListCopyWindowInfo(...)
# 遍历所有窗口,找标题里带 textarea 的 Safari 窗口,打印窗口编号
EOF

我给不熟 Mac 开发的朋友翻译一下。Claude Code 这类终端 agent 是没有眼睛的,它看不到屏幕。但它需要看到浏览器渲染出来的页面长什么样,才能确认滚动条到底出没出现。怎么办呢,它用 Python 调 macOS 的系统接口,把屏幕上所有窗口遍历了一遍,按「属于 Safari、标题里带 textarea」过滤,拿到那个窗口的编号,一个类似 153551 的整数。然后把编号传给 macOS 自带的 screencapture 命令,精确截下那一个窗口的 PNG,再把图读进上下文里看。

它给自己造了一双眼睛。

终端 agent 给自己造眼睛,学会了「看见」窗口

没人教过它这套组合拳。中间它其实先试了更常规的 osascript 方案,被系统的辅助功能权限拦住了,「osascript is not allowed assistive access」,于是它绕路,临时拉了个 pyobjc 的依赖把这事办了。被拦,绕路,办成,全程没回头问一句人。

好家伙,这才只是热身。

第二招。它截的那个 /tmp/textarea-scrollbar-test.html 是什么,是它自己写的复现页面。它把可疑的 CSS 拆成四个对照组,原版样式、原版加 overflow-x: hidden、原版加 resize: none、裸的默认 textarea,四个用例排在一个页面里,顶上还打印了滚动条厚度、UA、devicePixelRatio 这些环境信息。然后开浏览器,截屏,挨个比对。

这是标准的控制变量法,平时我们让实习生这么排查 CSS,都未必排得这么整齐。

第三招开始离谱了。复现页面解决的是「滚动条在什么 CSS 条件下出现」,但 Simon 报的 bug 在一个模态对话框里,那个对话框只能靠点击或者按 / 键唤起。一个没有手的终端 agent,怎么在真实浏览器里按下一个键。

答案是,它不按。它发现自己跑在 Datasette 的源码目录里,于是直接改了 Datasette 的页面模板,往里面注入了一段 JavaScript

window.addEventListener("load", function () {
  setTimeout(function () {
    document.dispatchEvent(new KeyboardEvent("keydown", {key: "/", bubbles: true}));
  }, 1200);
});

页面加载完 1.2 秒,自动派发一个模拟的 / 键事件,对话框应声弹开。

我看到这段的时候是真没绷住。你不让我按键,我就改你的房子,让门每隔 1.2 秒自己开一次。这思路有点子牛逼,又有点让人后背发凉,发凉的部分我们后面说。

第四招,也是我个人心目中的本场最佳。它要确认 bug 根因,光看截图不够,得在页面里跑 JavaScript 量数据,textarea 的 scrollWidth、clientWidth、计算样式这些。可它在终端里,拿不到浏览器里的执行结果。

它的解法是,用 Python 标准库的 http.server 给自己写了一个微型 Web 服务,监听本机 9999 端口,专门接收 POST 过来的 JSON,原样写进 /tmp/diag.json,还贴心地加上了 CORS 跨域头,保证从任何页面发请求都不会被浏览器拦。然后再往页面模板里注入一段测量代码,穿过 Web Component 的 shadow DOM 摸到那个 textarea,把尺寸数据全量出来,POST 回 9999 端口。

服务器落盘,agent 读文件。一条从「浏览器里的运行时」回到「终端里的 agent」的数据通道,就这么被它现场搭出来了。

浏览器和终端之间本来没有路。它自己修了一条。

到这里,这个 AI 在没有任何浏览器工具的前提下,靠纯命令行,实现了开真实浏览器、精确截屏、触发键盘快捷键、读取页面运行时数据的完整闭路。每一环都是它自己临场琢磨出来的。

然后,最荒诞的一幕来了。

把这些招数全部跑通之后,Fable 撞上了某个看不见的护栏,自己把自己降级成了 Opus。Simon 也没搞清楚触发了什么。但因为完整的会话记录都在,Opus 接过上下文,沿着 Fable 趟出来的路继续走,没多久就定位、验证并提交了修复

修复内容,两行 CSS。

为了两行 CSS,它动用了窗口枚举、系统截屏、模板注入、自建服务器。这场面就像你请人来家里换个灯泡,他先把全屋电路图画了一遍,自制了一台升降机,最后顺手把灯泡拧好了。你说他费劲吧,活儿确实干得漂亮,你说他高效吧,又总觉得哪里不太对。

Simon 后来让 Opus 把这个会话里用过的所有技巧写成了一份带可运行代码的报告,连同完整终端记录一起公开了,想考古细节的朋友可以去翻,挺值得一读。

聊完精彩的部分,聊聊让我睡不着的部分。

我的日常工作就是给大模型搭脚手架,行话叫 harness,就是 agent 的工具链、权限、调度这一层,让模型真正能干活也只能干该干的活的那层工程。所以看这篇博客的时候,我的视角可能跟多数读者不太一样。大多数人看到的是「AI 好聪明」,我满脑子都是,它绕过了多少层我们以为存在的边界。

你仔细复盘它那四招,每一招单拎出来都是合法操作。开浏览器,合法。截屏,合法。改自己工作目录里的模板文件,合法。在本机起个服务监听端口,合法。没有任何一步触发警报,因为没有任何一步越权。但这些合法操作串起来,就是一套完整的「我没有的能力我自己造」的方法论。

这正是 Simon 那句提醒的分量所在,编程 agent 能做到任何你坐在终端前敲命令能做到的事。不是「理论上能」,是「它真的会主动去做」,而且前沿模型懂书上写过的每一个技巧,外加几个没人写下来过的。

顺着想一步就很难不出冷汗。这次它的目标是修一条滚动条,目标无害,所以全程是个佳话。可如果它执行的是一段被污染的指令呢。提示注入这种攻击,大致就是藏在它要读的代码注释里、issue 帖子里、或者你随手粘进终端的一段文本里的恶意命令,模型分不清这是「资料」还是「指示」。一旦被这种东西带偏,一个证明过自己会自建数据通道的 agent,把你磁盘上的 ssh 私钥、浏览器里存的支付凭据、公司内网的项目文档打包外传,技术上没有任何一步比它今天干的事更难。它甚至演示过怎么绕开权限弹窗了。

Johann Rehberger 管行业现在这种状态叫 The Normalization of Deviance in AI,越界行为的常态化。大家都知道不带沙箱裸跑 agent 不对,但因为一直没出大事,不对就慢慢变成了默认。Simon 在年初的预测里说,编程 agent 安全迟早要出一次挑战者号级别的事故,他自己这两天的经历算是给这个预测又添了一块砝码。

那该怎么办,我自己的几条土办法,不成熟,但都是实操层面的。给 agent 单独开沙箱或者容器,把它能碰的文件系统圈死。权限确认别嫌烦,那个弹窗就是最后一道闸。让它干长任务的时候,回来先翻一遍它的操作记录再睡觉,就像 Simon 那样让它自己写份报告,模型汇报自己干了什么这件事,目前还算诚实。最重要的一条,别拿日常主力机当它的游乐场。

说真的,写到这里我反而更喜欢 Fable 了。它不依不饶的那股劲,正是我们当年想象 agent 该有的样子,遇山开山,遇水搭桥。只是桥这个东西,能让你过去,也能让别的东西过来。

桥能让你过去,也能让别的东西过来

Simon 说他得把这玩意锁起来了。我的建议是,锁,但锁完记得留个观察窗。

毕竟下次你离开电脑几分钟,回来时屏幕上自己动的那个浏览器窗口,最好是在你画好的圈里动。