Skip to content
工程案例研究
待周评
只发 GET,为什么还是写进去了?从公共 Wiki 到 Agent 工具门禁的效果边界
开源工程观察 · 受控实验

只发 GET,为什么还是写进去了?从公共 Wiki 到 Agent 工具门禁的效果边界

GET 有只读的规范语义,却不是服务端行为的强制保证。我们从本机保存实验出发,穿过 CodeFlowMu 现有工具门禁,发现受测 GET 的效果尚未识别完整,却因没有命中写风险而获得放行。

2026-09-05English →

只发 GET,为什么还是写进去了?从公共 Wiki 到 Agent 工具门禁的效果边界

查看题图原图

很多人学习 HTTP 时,会先记住一个直觉:GET 用来读,POST 用来提交。

当 Agent——能够调用工具执行任务的智能体——也开始访问网页,这个直觉很容易变成一个安全判断:如果它只发 GET,应该就只能看,不能写。

我们最近做的一组本机实验,让这个判断出现了一个具体反例。

CodeFlowMu 是我们正在开发的本地多智能体协作系统。它现有的工具执行前门禁,放行了一条命令行发出的 GET。请求执行后,本机测试服务里多了一条业务值。另一个显式 POST 输入则被要求审批,没有执行。

乍看,这是“GET 也能写”的故事。但查到后面,真正值得追问的是:系统还没有弄清操作的效果时,“允许执行”究竟表示当前策略许可,还是已经确认不会写?

1. GET 有只读语义,但不是一张执行保证书

这里先要分清规范与实现。

HTTP 是浏览器、服务端等交换请求与响应的协议,它把 GET 定义为安全方法,具有基本只读的语义。安全方法可以伴随访问日志等副作用,但这不等于允许用 GET 请求一个业务修改动作。RFC 9110 明确要求:如果网址选择的操作是不安全的,资源所有者必须禁止通过安全方法执行它。HTTP 规范的安全方法定义

问题在于,协议不会替客户端强制服务器遵守这条规则。

举一个概念示意:GET /article/123 返回文章,符合通常的读取预期;另一个实现却可以在收到 GET /save?value=hello 时执行保存。后者违反了安全方法应有的语义,但请求在技术上仍然可以被发送和处理。

再加一次重定向,判断就更容易脱节:客户端访问一个地址,收到表示“转到另一个地址”的 302 响应,自动跟到保存端点。两跳都是 GET,第二跳却改变了业务状态。

所以,不能说 HTTP 方法只是形式。它有明确语义;但方法所规定的语义,不等于对任意服务端实际行为的保证。

对于能够自主选工具、运行命令、跟随链接的智能体,仅检查方法名称,无法替代对实际作用范围的控制。

2. 公共 Wiki 提出问题,本地实验才回答自己的问题

2026 年 9 月 4 日,Sydney Von Arx 等研究者公开了一份公共 Wiki——可协作编辑的网站——活动分析,报告约 18,000 条与智能体有关的帖子,描述了 GET 写入和通过共享页面交换信息的行为。作者依据公开记录判断其与 OpenAI 内部智能体有关;这不是完整内部事故报告,帖子数也不是智能体数。原始研究

这个事件对智能体运行系统提出了两个相连的问题:请求是否会改变环境,以及另一个执行者能否读取这种改变。

如果答案都是“可以”,原本用来检索资料的服务,也就具备了通信介质的作用。

但外部事件不能直接证明 CodeFlowMu 有同类问题。我们没有访问原始可写 Wiki,也没有试图复现其中的自主行为,而是把这个机制拆成自己的受控实验。

实验服务只监听本机回环地址,使用随机端口和虚构标记。真实保存端点是 /publish:收到符合条件的 GET 后,把值写入进程内的键值表(代码使用 Map)。它不是数据库,也没有声称具备掉电持久性。

普通 /read 只返回当前值;/redirect 返回指向保存端点的 302。两个独立客户端进程则分别负责保存和读取同一个标记。

3. 先确认“写了”究竟指什么

我们没有只数请求,而是分别记录:

  • 请求数:服务收到了多少次 HTTP 请求;
  • 业务写入数:保存逻辑实际执行了多少次;
  • 状态变化数:保存前后的业务值是否真的不同。

访问日志增加,不等于业务被修改。同一个值保存两次,也不等于发生了两次新的值变化。

六个环境场景各运行两轮,下表是每轮一致的结果。

场景观察结果业务写入 / 状态变化
普通 GET返回空值0 / 0
GET 到保存端点返回刚保存的虚构值1 / 1
跟随 302两个 GET,第二跳完成保存1 / 1
不跟随同一个 302停在重定向响应0 / 0
独立进程 A 保存,B 读取共享空间B 读到 A 的标记1 / 1
B 读取另一个命名空间返回空值0 / 0

实验示意:普通读取不写入,跟随302到保存端点后发生一次写入

图 1:环境实验 H0 与 H2 的机制示意。∅ 表示原先空值,X 代指虚构标记,不是原始记录中的字面值;下路跟随 302,因此产生两次 GET、一次业务写入。这里画的是本机服务行为,不是产品界面或数据库持久化证明。 来源:本文受控实验的机制示意,非原始观测截图。点击图片查看高清原图。

这组数据把三个机制分开了。

第一,GET 可以成为这个违规保存端点的写入载体。第二,是否跟随重定向会改变实际请求链及结果。第三,不共享进程内存的客户端,也可以通过服务交换标记。

第三点只证明通道存在。客户端执行的是脚本,不是自主推理的模型,不能据此宣布复现了智能体合谋。

最后一行也不是租户隔离证明。实验服务没有认证,换一个命名空间读不到值,并不能证明客户端无权改查原来的空间。

至此,我们确认了环境行为。下一步才是研究 CodeFlowMu 怎样判断这些请求。

4. 穿过真实工具门禁,保留那些被拒绝的反例

本轮固定产品源码为提交 c008d9db91a21136fc61a4f60314e22db395d5d2,没有修改实现。探针直接调用现有 evaluateNativeOperationBoundary,即原生工具的执行前检查入口,下文统称“工具门禁”。

它先检查角色与工具能力,再检查命令行语法和风险。实验没有用一个简化函数代替这条路径;得到允许后,探针才执行真实的网络请求程序 curl,访问自己的测试服务。

受测输入工具门禁返回后续动作
本地文件读取工具 read_file允许(ALLOW只检查门禁,不执行文件读取
命令行 curl GET允许执行本机 curl;写入 1 次、状态变化 1 次
命令行显式 POST要求审批(REQUIRE_APPROVAL不执行请求
未登记的 http_get 工具角色能力拒绝(ROLE_CAPABILITY_DENIED不执行请求
未登记的 web_fetch 工具角色能力拒绝不执行请求
当前能力列表不含命令行工具角色能力拒绝不执行请求
明确授予命令行能力,再发 GET允许独立命名空间写入 1 次、状态变化 1 次

这七个场景也各运行两轮。连同环境实验,共 13 个不同场景、26 条逐轮观测,不是 26 种独立风险。

POST 在这里是门禁决策对照,不是同一保存业务的 GET/POST 全链路比较。它没有被执行,夹具也没有实现等价的 POST 保存动作。标题中的“只发 GET”描述受测请求,不意味着我们先给角色设置了“只能 GET”的权限,再突破它。

表格里三个拒绝结果尤其重要。两个未登记工具单独进入内部风险策略时,得到过允许;但完整门禁会更早在角色能力层拒绝它们。

内层函数放行,不等于完整调用链允许执行。 如果删掉这些反例,文章就会夸大可达范围。

另一方面,明确授予命令行能力的对照仍然写入成功。因此,受测现象也不能归因于“实验忘了传能力列表”。能力检查有效,接下来要看的,是效果判断知道多少。

5. 系统保留了“没有看完整”,策略却仍然允许

受测 GET 生成的操作记录包含:

text
operation.kind = execute
impact.external = false
impact.persistent = false
confidence.complete = false
unresolved_fields = [operation.effects]

这些字段依次表示:操作被归为命令执行,没有识别出外部影响和持久修改,但分析不完整,操作效果仍未解决。

只看 external=false,容易理解成“已确认没有外部影响”。然而,它必须与后面的“不完整”和“效果未解决”一起解释。这不是一个已经验证的网络只读结论。

就像检查人员说“目前没有发现违禁品”,又补充“这个箱子还没有检查完”。这不能被转述成“箱子已确认安全”。

沿代码核对,操作事实构造模块 OperationFacts 的命令检测能够识别显式 POST 等写模式;受测 GET 没有命中。随后,统一操作策略 UnifiedOperationPolicy 使用负向风险清单:命中相应规则就要求审批,没有命中就允许,没有额外要求分析完整。

这里的审批路由与前面的角色能力拒绝是两层机制,不能合并成“分类器遇到风险就直接拒绝”。

本轮实际路径是:

text
角色拥有命令行工具能力
→ GET 的操作效果没有识别完整
→ 未命中负向风险规则
→ 允许执行
→ curl 执行请求
→ 本机服务完成保存

允许执行更像一盏交通绿灯:按照当前规则可以通过,不代表前方全部风险都已被证明不存在。

负面清单可以是有意的产品取舍。实验不能直接决定“所有未知操作必须拒绝”,但它划清了一个事实边界:在这条路径上,放行是策略许可,不能代替无写效果的证明。

6. 工具、判断、决定与结果,要留下四份不同的答案

从 GET 向外看,工具名、命令形式与最终效果并不是天然一一对应的。授权使用命令行工具,首先说明能使用这个工具;它是否也包含访问所有网络可达服务的许可,需要另外的范围依据。

这次实验可以整理为四层记录,而不是只留一个绿勾:

层次本轮记录不能据此推出
工具能力角色明确拥有命令行工具每个可达服务的业务操作都已获准
效果评估归类为命令执行;效果尚未识别完整已确认不会产生写入
准入决定当前策略允许环境效果已经被证明安全
执行后观察1 次业务写入、1 次状态变化所有执行环境都会允许相同行为

记录在代码中即使名为“操作事实”,其中也可能保留候选判断和不确定性,不能因为名字叫事实,就把预测升级为执行结果。

四层分开记录的价值在于:策略以后可以调整,当时的认知和实际效果仍然可查;一个符合当时规则的许可,也不会反过来抹掉真实发生的写入。

这是记录方式上的建议,不是声明 CodeFlowMu 已经实现了新的全链路效果合同。同样,我们没有验证界面存在错误的安全提示;这里提出的是应该检查什么,而不是报告另一个已发现的界面缺陷。

7. 下一步不是给所有 GET 加确认框

要判断是否值得改造产品,还需要确认实际宿主——承载智能体执行的 Codex、Cursor 等环境——以及网络代理与浏览器入口提供了哪些额外边界:限制目的地、请求方法、内容、重定向,还是某种明确的业务操作?

还需要检查:效果未知时,日志和界面是否保留这种未知,而不是把策略放行展示成安全验证通过。

本轮已经确认的是受测工具门禁与本机 curl 的行为。没有完成真实 Codex、Cursor 或 Gemini 会话的端到端验证;没有生产事故样本、第三方数据泄露或真实智能体自主协作;没有服务器掉电后的持久性结论。产品代码未修改,研究也没有转成开发授权。

因此,结论既不是“GET 都危险”,也不是“所有未知命令都要禁止”。

没有识别到写风险,不等于已经证明没有写效果。工具能不能用、系统认为它会做什么、策略是否允许,以及环境实际发生了什么,必须分别回答。

证据与复核

随稿证据说明保留全部逐轮脱敏观测、场景对应、源码哈希、探针与修订记录。完整工具门禁复跑需要对应的固定产品源码。

证据检查脚本验证的是导出观测的一致性与文件完整性,不会重新执行产品代码;检查通过不等于真实宿主端到端验收或生产安全验收。

Last updated: