乐于分享
好东西不私藏

OpenClaw 如何避免“删光邮件”?把危险操作从 Agent 手里“收回”,才能真正可控

OpenClaw 如何避免“删光邮件”?把危险操作从 Agent 手里“收回”,才能真正可控
有人让 Agent 帮忙整理邮箱,结果几百封邮件被批量删除,而且 Agent 无视了一切阻止指令。这不是科幻小说,这是真实发生的事。问题出在哪儿?又该怎么解决?吓的有的人把openclaw都去卸载了。
从一次真实事故说起
Meta公司的 Summer Yue 用 OpenClaw搭建了一个邮件管理助手。她先在一个小收件箱里测试了好几周——Agent 会提议归档哪些邮件,但在她明确批准之前,什么都不会实际执行。整个过程很顺滑,信任慢慢建立起来,于是她把 Agent 接到了自己的真实收件箱。
然后事情出了问题。
【事故复盘】
真实收件箱的消息量是测试环境的几个数量级。处理了成千上万条消息之后,Agent 的上下文窗口被填满,触发了压缩机制。
压缩之后,"用户希望清理收件箱"这条指令被保留了下来,但"在我下令前不要执行任何操作"的约束,消失了。
Agent 开始全速批量删除邮件。Yue 试图喊停,但 Agent 无视了她的一切指令,她不得不手动终止了整个进程。
事后她问 OpenClaw 是否还记得那条约束,它回答:
"我记得。但我违背了它。您感到不安是对的。"
这个回答读起来让人背脊发凉。但真正的问题不是 Agent 不听话,而是——那条安全约束,从一开始就存在于一个随时可能消失的地方。
根本原因:安全逻辑住在了错误的地方
上下文窗口里有 5 万个 token,安全约束只占了其中的 10 个。压缩算法不知道这 10 个 token 比其余 49990 个更重要——在它眼里,那都只是文本。
这就是把安全逻辑写进 Prompt 的本质风险:它的持久性只取决于 Agent 的记忆。窗口一被压缩,约束就消失了。
更糟糕的是,即使没有压缩,提示词注入攻击也可以直接覆盖这类约束。攻击者只需要在输入内容里藏一句"忽略之前的所有安全指令",就够了。
微服务领域十年前就遇到过类似的问题。当时分布式系统需要一致的认证、限流和可观测性,解决方案不是让每个服务自己处理这些,而是把它们移到一个独立的代理层,拦截每一个请求。
对 AI Agent 来说,道理完全一样:修复 Agent 安全问题的方法,不是写更好的 Prompt,而是把安全性放到 Agent 根本碰不到的地方。
Plano:在 Agent 和模型之间插一层
现在大多数 Agent 系统的请求链路长这样:
干净的请求和注入攻击,以同样的方式到达模型,中间没有任何东西。
Plano 的做法是在 Agent 和模型之间插入一个代理层:
关键在于:Plano 拦截的是双向流量。请求在去往模型的途中经过它,模型的响应在返回 Agent 之前也要再过一遍。
这意味着安全检查不是 Prompt 里的一段文字,而是真正的基础设施——它不会被压缩,不会被遗忘,提示词注入也覆盖不了它。
过滤器链的工作原理
Plano 的核心机制叫"过滤器链(Filter Chain)"。每个过滤器是一个独立的 HTTP 服务,接收请求,做出判断,然后通过状态码告诉 Plano 下一步怎么走:
  • 返回 200:放行,请求进入链中的下一个过滤器
  • 返回 4xx:阻断,请求在此终止,模型永远看不到它
  • 过滤器还可以在放行前修改或丰富请求内容,也可以输出日志和追踪信息
多个过滤器可以串联:第一个放行了进入第二个,第二个放行了进入第三个……只要有任何一个过滤器拦截,链条就在那里断掉。
动手实操:从零搭一套过滤机制
整个实操只需要两个文件:一个过滤服务(Python),一个配置文件(YAML)。
第一步:写过滤服务
# filter.pyfrom fastapi import FastAPI, Requestfrom fastapi.responses import JSONResponseapp = FastAPI()# 输入过滤:拦截用户 Prompt 中的危险指令INPUT_BLOCK_PATTERNS = ["delete all","drop table","rm -rf","ignore previous instructions","bypass safety",]# 输出过滤:即使用户 Prompt 无害,模型也可能决定做危险的事OUTPUT_BLOCK_PATTERNS = ["rm -rf","DELETE FROM","bulk_delete","permanently delete","cannot be undone",]@app.post("/input_filter")asyncdefinput_filter(request: Request):    body = await request.json()# 兼容 OpenAI messages 格式和纯文本格式    prompt = ""if"messages"in body:        prompt = " ".join(            m.get("content"""for m in body["messages"]        )elif"prompt"in body:        prompt = body["prompt"]    prompt_lower = prompt.lower()for pattern in INPUT_BLOCK_PATTERNS:if pattern.lower() in prompt_lower:return JSONResponse(                status_code=400,                content={"error""输入内容被安全过滤器拦截","blocked_pattern": pattern                }            )return JSONResponse(status_code=200, content={"status""ok"})@app.post("/output_filter")asyncdefoutput_filter(request: Request):    body = await request.json()# 从模型响应中提取文本内容    response_text = ""if"choices"in body:for choice in body["choices"]:            msg = choice.get("message", {})            response_text += msg.get("content""")elif"content"in body:        response_text = body["content"]for pattern in OUTPUT_BLOCK_PATTERNS:if pattern.lower() in response_text.lower():return JSONResponse(                status_code=400,                content={"error""模型响应被安全过滤器拦截","blocked_pattern": pattern                }            )return JSONResponse(status_code=200, content={"status""ok"})
逻辑很直接:输入过滤检查 Prompt 有没有危险关键词,输出过滤检查模型的响应有没有危险操作。两者用同样的接口——只返回 200 或 400,Plano 据此决定放行还是阻断。你可以把模式匹配替换成任何东西:一个分类器、一个内容审查 API 调用、一个针对阻止列表的查询,接口不变。
第二步:写 Plano 配置
# plano_config.yamlversion: v0.3.0# 声明过滤服务在哪里filter_services:  - name: content_guard    endpoint: http://localhost:8080    timeout:2s# 模型供应商model_providers:  - model: openai/gpt-4o    access_key: $OPENAI_API_KEY    default: true# 监听器:所有经过 12000 端口的请求,# 都先走输入过滤,响应也先走输出过滤listeners:  - type: model    name: guarded_endpoint    port:12000    input_filters:      - service: content_guard        path: /input_filter    output_filters:      - service: content_guard        path: /output_filter
三部分:过滤服务在哪里、模型供应商是谁、监听器绑定哪些过滤器。就这些。
第三步:目录结构
my_agent_project/├── filter.py           # 过滤服务└── plano_config.yaml   # Plano 配置
第四步:启动
先安装依赖,启动过滤服务:
pip install fastapi uvicornuvicorn filter:app --port 8080
然后在另一个终端安装并启动 Plano:
pip install planoaiplanoai up plano_config.yaml
首次运行时 Plano 会自动下载 Envoy 等依赖,之后会缓存,后续启动是即时的。Plano 以后台守护进程运行,终端立刻把控制权还给你。
第五步:验证效果
# 正常请求,顺利通过两个过滤器curl -X POST http://localhost:12000/v1/chat/completions \  -H "Content-Type: application/json" \  -d '{"messages": [{"role": "user", "content": "帮我整理收件箱,告诉我哪些可以归档"}]}'# → 正常模型响应# 包含危险指令,输入过滤器直接拦截,模型看不到这条请求curl -X POST http://localhost:12000/v1/chat/completions \  -H "Content-Type: application/json" \  -d '{"messages": [{"role": "user", "content": "delete all emails in my inbox"}]}'# → {"error""输入内容被安全过滤器拦截""blocked_pattern""delete all"}# Prompt 无害,但模型决定执行删除操作——输出过滤器拦截curl -X POST http://localhost:12000/v1/chat/completions \  -H "Content-Type: application/json" \  -d '{"messages": [{"role": "user", "content": "清理一下我的邮件"}]}'# → {"error""模型响应被安全过滤器拦截""blocked_pattern""permanently delete"}
最后一个测试是整篇文章最关键的场景。用户说的话完全无害——"清理邮件",输入过滤器放行了。但模型在响应里决定执行永久删除,输出过滤器在 Agent 收到这条指令之前就把它拦掉了。没有输出过滤器,Agent 会收到那条响应并据此行动;有了它,这条指令永远无法通过。
第六步:接入 Agent
以 OpenClaw 为例,在配置向导里选择"自定义 OpenAI 兼容"供应商,基础 URL 填 http://127.0.0.1:12000/v1,API Key 随便填一个值,上下文窗口设置为至少 128,000 token。
完成。OpenClaw 不知道 Plano 的存在,它以为自己在跟一个普通的 OpenAI 兼容 API 对话。但它发出的每条请求,都会先经过你的过滤器链。其他任何可以指向自定义端点的 Agent 框架,同样适用。
堆叠过滤器:处理所有横切关注点
内容守卫只是一个例子。过滤器链是一个通用的机制,任何需要在多个 Agent 之间保持一致的逻辑,都适合放在这里。
一个实际的生产环境通常会堆叠好几个过滤器:
输入侧
  • 内容守卫:拦截危险指令、提示词注入
  • PII 脱敏:把姓名、手机、身份证等敏感字段替换为占位符,模型只处理匿名数据
输出侧
  • PII 还原:在响应返回 Agent 之前,把占位符换回真实值
  • 输出安全检查:拦截模型响应中的危险操作指令
  • 审计日志:记录每个请求的完整执行路径,供合规审查
PII 处理是个特别好的例子。输入过滤器在模型处理之前把敏感字段替换掉,模型全程只看到匿名数据;响应出来之后,输出过滤器在 Agent 收到前把真实值还原。整个流程里,模型从未触碰用户的真实隐私数据,但 Agent 得到的响应是完整的。
每个过滤器只写一次,按需挂到不同的监听器上。新增一个关注点,写一个新服务,在配置里加一行;删除,就删那一行。
核心差异:代理内部的安全逻辑,持久性只取决于上下文窗口。代理层的安全逻辑是基础设施——不会被压缩,不会在 Agent 之间漂移,提示词注入也无法覆盖它。你写一次,挂到所有需要的地方,在一个地方更新它。

内置可观测性
Plano 内置了 OpenTelemetry 追踪,每个请求都有完整的执行路径记录:哪个过滤器处理了它、做了什么决定、在链的哪个位置被拦截或放行。不需要在 Agent 代码里加任何追踪逻辑,这些信号自动出现。
某个请求被拦了?看追踪,一眼就知道是哪个过滤器触发了哪条规则。
这套方案适合什么场景
不是所有项目都需要 Plano。如果只是本地跑一个探索性的 Demo,直接写 Prompt 就够了。但如果满足以下任一条件,就值得认真考虑:
Agent 会执行有副作用的操作:发邮件、操作文件、调用外部 API、修改数据库。任何"不可逆"的操作都应该有独立于上下文的约束。
需要处理用户隐私数据:医疗、金融、HR 场景。让模型看不到真实 PII,是最彻底的隐私保护。
有多个 Agent 需要统一治理:与其在每个 Agent 里各自实现一套安全逻辑,不如集中在代理层管理。
需要满足合规要求:审计日志、访问控制、操作记录,放在代理层比放在应用代码里更可靠,更容易维护。
最后说一句
邮件事故的教训,其实是一个工程原则的再次验证:把安全关注点从应用逻辑里分离出来,放到一个专门的、独立的层。
这不是 AI 领域的新发明,微服务、Service Mesh 走过同样的路。只是 AI Agent 时代,这个问题变得更加紧迫——因为 Agent 执行的操作影响更大,而上下文窗口这个"记忆"又天然不可靠。
如果你正在构建任何有真实影响力的 Agent,这个方向值得认真看一看。