🔶 我们给一个编程 Agent 配上了致命三要素:数据、互联网和公开仓库
英文标题:🔶 We gave a coding agent the lethal trifecta: data, internet, a public repo
该团队让 Claude Code 驱动的编程 Agent 通过 Slack 中的 🦀 请求自动创建 GCP 开发环境、修复问题、进行自我审查并录制功能演示,再由人工决定是否发布 PR。为了排查真实系统问题,Agent 被授予对 Slack、Kubernetes、通话录音和产品分析数据的只读访问,但同时也具备联网能力及向公开 GitHub 仓库推送代码的能力,形成了安全领域所称的“致命三要素”。团队使用 OpenAPPA 在 Agent 读取内部数据后限制其向公共目的地发送信息,并对 MCP 和 CLI 工具调用实施策略检查。作者认为,这套机制尚不适合独立处理需要架构判断或审美取舍的复杂任务,但已将小型缺陷修复的成本降至提交一份 bug 报告的水平。
7.4 KB
71.5 KB
16.3 KB
80.3 KB
46.0 KB
8.2 KB
6.7 KB
57.0 KB
71.5 KB
7.4 KB
中文
让一个编程 Agent 对 Slack 和 Kubernetes 具有只读访问权限,可以帮助它调查 bug,但这并不能阻止 Agent 将其读取的内容复制到一个 Web 请求或公开提交中。
我们的 Slack 机器人需要这些访问权限才能真正发挥作用。因此,除了可随时销毁的开发环境和自动化 PR 审查外,我们还必须构建针对数据流向的控制机制。本文将跟随一个真实 bug 穿过这套配置:从 Slack 里的一个 🦀,到合并后的 PR。
如果你主要是为安全细节而来,请跳至“令人不安的部分”。
案例研究
我们的工程师 Mark 发现了聊天 UI 中的一个小 bug:一旦上下文压缩开始,在会话结束前你就无法输入另一条消息。多任务处理时这相当令人烦恼。
通常,这意味着要创建工单、切换上下文,并失去工作节奏。或者,这个 bug 会和另外六十个待办事项一起被关进 backlog,所有人都同意它应该被修复。
现在,我们只需在 Slack 中发一个 🦀 emoji 就能修复这类 bug。
Mark 希望消息编辑框在压缩期间仍能继续接受并排队消息。他发布了一条简短请求,并附上一张截图:

Bug 报告:我们的 #task-feed 频道里一条以 🦀 结尾的一句话消息,附带一张截图。没有工单、分支名称或复现步骤部分。
大约三分钟后,机器人回复了访问信息。以下是其中一条消息:

Crab Bot 发布了一条 SSH 命令、一个应用 URL,以及引导会话的说明。
这三分钟里发生的事情是:运行在我们集群中的控制器从 Crab Bot 接收请求,并在 GCP 中配置了一台虚拟机。
该虚拟机预先配置了:
最后,它会启动一个 Claude Code 会话,指示 Claude 首先阅读 Slack 线程以收集任务上下文;整个过程没有人工监督。
如果你通过 SSH 登录,VM 登录界面如下:

VM 登录横幅:任务 ID、分支,以及连接至 Claude 会话的说明。
当然,主角是那只一只钳子拿着 Archestra、另一只钳子拿着 OpenAPPA 的螃蟹。这是有原因的,我们稍后会谈到;但现在先说说值得注意的积极之处。
你可以持续与 Agent 对话
Agent 会轮询它自己的 Slack 线程。这使你可以直接向正在运行的会话添加任何消息,而消息会在执行过程中被纳入工作。一个过去需要耗费时间的后续想法,如今只需要在你已经阅读的线程中发一条消息。
这里还有一个我很喜欢的小细节:会话接收的并不是你的消息文本。它首先获得的是指向 Slack 线程的一个指针,然后自行读取该线程。这一点尤其重要,因为我们的许多 bug 报告都是截图。因此,Agent 不必依赖他人对截图的描述,而是在开始工作前打开原始线程并检查实际图片。这是排除人为因素的绝佳方式。
Agent 先审查自己的工作
PR 创建后,会话会运行 nitpicker,这是我们同事 Arseny 开发的一个工具。该工具同时用多个模型审查 diff,汇总它们的结论,并在 PR 中发布一条评论。
随后,会话将该评论视作针对自身工作的审查反馈。它会修复有效问题,并解释为何其余部分保持不变。
这样一来,第一个打开 PR 的人看到的工作已经经过了一轮严格审查,所有分歧也都被记录下来。
接下来是我最喜欢的部分。
会话会录制自己的演示
当改动可以展示时,会话会录制刚构建功能的屏幕视频,并将其发回 Slack 线程。为此,我们使用 shot-scraper video,这是 Simon Willison 于 6 月发布的、由 Playwright 驱动的工具。这个集成和它的描述一样简单:传入配置,获得视频。Simon,如果你正在读这篇文章:感谢你让我们免于自行构建一个更小、更差的版本。
结果
以下是 Mark 的 bug 最终的处理结果。左侧是修复演示:Agent 打开我们的聊天应用,发送一条消息,等待“正在压缩对话上下文……”出现,然后在编辑框中输入一条后续消息——这正是当天早上还完全不可能做到的事!右侧则是 Mark 提出请求的 Slack 线程,其中嵌入了视频;Mark 回复“Ship it”,随后又说“Promote”,机器人则回复了公开 PR 的链接。任务完成。
观看由 Agent 录制的功能演示
再次强调:没有人配置环境,也没有人拉取分支。整个流程中,人类唯一的操作是:
- 描述 bug
- 观看视频
- 输入“Ship it”
- 审查 PR
令人不安的部分
现在,很多注意力已经放在处理小 bug 上了,让我们把规模稍微放大一些。
我们的某些任务并不是“修复这个按钮”,而是“我们的内部 staging 部署很慢,去查明原因”。
Agent 不能仅靠阅读 diff 回答这个问题。它需要查看正在运行的系统(Pod、重启、OOM kill、容器日志、资源限制)。它可能还需要通话录音、产品分析数据,或者一个已有三人诊断出一半问题的 Slack 线程。
我们为这些会话提供了对如下内容的读取权限:
- Claude 会话注册了一个 MCP 网关。
- 它运行在我们自己的 Archestra 部署中。
- 它可以读取我们的 staging Kubernetes 集群、Slack、通话录音和产品分析数据。
这些工具在设计上均为只读,并且每一个工具均使用申请环境者本人的凭据运行,因此会话无法超出请求者的访问权限范围。
让我们再看一遍我们构建的东西:
- 一个能够读取内部数据的 Agent(客户名称、租户数据、集群内部信息、生产环境截图)。
- 同一个拥有网络连接和 Web 工具的 Agent。
- 同一个能向 GitHub 推送代码的 Agent,而我们的平台仓库是公开的。
这正是我们有意接入的、在一台无人看守机器上运行的致命三要素。如果某个会话决定在提交信息中写入请求者的姓名,没有人工会在流程中拦住它。
你可以在 prompt 中加入一条规则来阻止这种行为,我们确实这么做了:其中有完整的保密部分,要求 Agent 用中性的技术语言描述工作,且绝不提及客户名称。但 prompt 指令的可靠性取决于模型的状态——这不是你能称之为“直接控制”的东西。
我写过一篇文章,讨论为什么模型在结构上易受这类攻击影响;还有一篇文章讨论当你给 Agent 过多访问权限并期待一切顺利时会发生什么。这又让我们回到 Simon Willison。他在今年早些时候写道,他曾公开预测,鉴于编程 Agent 对这类攻击的脆弱性,2026 年将发生“一场关于编程 Agent 安全的挑战者号灾难”。
OpenAPPA 会追踪 Agent 读取的内容,并检查它可以向何处发送数据。
我们在它前面设置了什么
OpenAPPA 是一个本地策略引擎。它会追踪 Agent 已读取数据的限制,并在每次工具调用运行前进行检查。在我们的配置中,规则如下:
- 会话开始时不受限制。
- 从内部工具读取内容会将会话标记为内部会话。
- 一旦被标记为内部会话,未经授权审查,它就不能发布到公共目的地——例如,不能将内部日志和配置复制到公开的 GitHub issue 中。
这对我们的 bug 报告意味着什么?
读取内部 Slack 线程后,Agent 不能简单地将其获知的信息发布到公开 GitHub issue。OpenAPPA 会在工具调用运行前将其阻止。
Agent 需要原始线程和截图来理解 bug。赋予它这类访问权限,不应同时赋予它发布其中发现的内部信息的权限。
OpenAPPA 同时覆盖 MCP 工具和 CLI 工具。例如,我们添加了一个脚本,用于检查 kubectl 命令读取的是哪个集群。如果是非本地集群,输出将被标记为内部信息。该脚本还会将来自云 API 的读取标记为内部信息,同时不影响普通的本地开发。
如果 Agent 用脚本无法识别的方式伪装命令,我们还会使用 OpenAPPA 内置的 LLM 分类器作为第二道防线。它会评估我们尚未手动标注的调用,并帮助识别读取内部数据的命令,以便对其施加相同限制。
OpenAPPA 采用 MIT 许可证,目前处于预览阶段;论文已经发布,策略参考也已公开,但它仍是 RFC,而非完整成品。我们在自己的 bug 修复会话中使用它,以观察当 Agent 需要内部数据才能完成有用工作时,这些策略是否经得住考验。
我们宁愿成为乐观案例的证据,也不愿因重大事件登上新闻头条。Simon,如果你想尝试攻破它,请务必来试试。这可能正是这套系统所需要的压力测试。
结论
它擅长的事情:小型、描述清晰、自包含的修复。
它不太擅长的事情:任何需要品味、架构判断或辩论的工作。我们不会让它独立处理难题,因为这些问题要求我们“处于循环之中”。它的 PR 会先在私有镜像中创建,只有当人类说出“Promote”时,才会发布到公开仓库。
这个工具并没有让我们在困难工作上更快,但它将修复一个小 bug 的成本降低到了提交一份 bug 报告的成本。而事实证明,大量 bug 的修复成本远低于我们已经习惯认为的水平,也更容易修复。
在线程里放一只螃蟹。然后回去做真正重要的事。
English / 原文
Giving a coding agent read-only access to Slack and Kubernetes lets it investigate bugs, but it doesn’t stop the agent from copying what it reads into a web request or a public commit.
Our Slack bot needs that access to do useful work. So alongside the disposable dev environments and automated PR reviews, we’ve had to build controls over where the data can go. In this article I’ll follow one real bug going through the setup, from a 🦀 in Slack to a merged PR.
If you’re mainly here for the security details, skip to
“The uncomfortable part.”
The case study
Our engineer Mark caught a minor bug in our chat UI: once context compaction starts, you can’t type another message until the session finishes. Pretty annoying when you’re multitasking.
Usually, this means a ticket, a context switch, and lost momentum. Or the bug joins sixty other inmates in the backlog while everyone agrees it should be fixed.
We now fix such bugs by dropping a 🦀 emoji in our Slack.
Mark wanted the composer to keep accepting and queuing messages during compaction. He posted a short request with a screenshot:
The bug report: a one-liner in our #task-feed channel that ends with 🦀, with a screenshot attached. There’s no ticket, branch name, or section with repro steps.
About three minutes later, the bot replies with access details. Here is one of those messages:
Crab Bot posting an SSH command, an app URL, and instructions for steering the session.
What happened in those three minutes was that a controller running in our cluster picked up the request from the Crab Bot and provisioned a virtual machine in GCP.
The virtual machine comes pre-configured with:
Finally, it starts a Claude Code session pointing Claude to start by reading the Slack thread to gather context on the task, all this with no human supervision.
Here’s how the VM login screen looks like if you SSH in:
The VM login banner: the task id, branch, and instructions to attach to the Claude session.
The main character of course is the crab holding Archestra in one claw and OpenAPPA in the other. There’s a reason for this, and we’ll talk about it later, but now, to the positives worth noting.
You can keep talking to the agent
The agent polls its own Slack thread. This allows you to add any message straight to the running session, and it will be folded into the work on the go. A follow-up thought, which used to be time-consuming, now costs one message in the thread you are already reading.
There’s also a small detail I like here. The session doesn’t receive your message as text. It first gets a pointer to the Slack thread and then reads the thread itself. This is especially important because many of our bug reports are screenshots. So instead of relying on someone else’s description, the agent opens the original thread and inspects the actual image before starting work. This is a perfect way to exclude the human factor.
The agent reviews its own work first
Once the PR is open, the session runs
, a tool built by our colleague Arseny. The tool reviews the diff with several models at once, merges their verdicts, and posts one comment on the PR.
The session then treats that comment as review feedback on its own work. It fixes valid issues and explains why it leaves the rest unchanged.
This way the first human to open the PR sees the work that has already survived a critical pass, with all disagreements written down.
And now to the part I enjoyed the most.
The session records its own demo
When the change can be presented, the session makes a screen recording of a freshly built feature and posts it back into the Slack thread. For this, we use
, a Playwright-powered tool Simon Willison
. The integration is about as complicated as the description: pass it a config, get a video back. Simon, if you’re reading this: thank you for saving us from building a smaller, worse version ourselves.
The result
Here is the end of Mark's bug. On the left is the demo of the fix: the agent opens our chat app, sends a message, waits for "Compacting conversation context…" to appear, then types a follow-up into the composer—exactly the thing that was impossible that morning! On the right is the Slack thread where Mark requested it, with the video embedded, Mark replying "Ship it", then "Promote", and the bot replying with a link to the public PR. Job done.
Watch the agent-recorded feature demo
To underline once again: no one either set up an environment or pulled a branch. The only human actions in the whole loop were:
- Describing the bug
- Watching the video
- Typing in "Ship it"
- Reviewing the PR
The uncomfortable part
Now that a lot of attention has been paid to handling minor bugs, let’s scale things up a bit.
Some of our tasks are not “fix this button”. They're “our internal staging deployment is slow, go figure out why.”
An agent cannot answer that just by reading a diff. It needs to look at the running system (pods, restarts, OOM kills, container logs, resource limits). It might need call recordings, product analytics, or the Slack thread where three people have already diagnosed half of it.
We gave these sessions read access to exactly that:
- The Claude session is registered with an MCP gateway.
- It runs in our own Archestra deployment.
- It can read our staging Kubernetes cluster, our Slack, call recordings, and product analytics.
Each of those tools is read-only by construction, and each one runs with the credentials of the person who asked for the environment, so a session can’t go beyond the access level of its requester.
Let’s look once again at what we've built:
- An agent with read access to internal data (customer names, tenant data, cluster internals, screenshots of production).
- The same agent with a network connection and web tools.
- The same agent pushing code to GitHub, which is public for our platform repo.
That's the very
that we wired up on purpose, on a machine with nobody watching it. If a session ever decides to put a requester’s name in a commit message, there is no human in the loop to catch it.
You can include a rule in the prompt to block this behavior, which we do: there's a whole confidentiality section instructing an agent to describe its work in neutral technical terms and never name a customer. But prompt instructions are as reliable as the model's mood—not something you can call “direct control”.
There’s a post I wrote about
why models are structurally vulnerable to this
, and another about
what happens when you give an agent too much access and hope for the best
. Which brings us to Simon Willison again, who wrote earlier this year that he is on the record predicting
"a challenger disaster for coding agents security"
in 2026, based on how vulnerable coding agents are to precisely this kind of attack.
tracks what the agent reads and checks where it can send data.
What we put in front of it
OpenAPPA is a local policy engine. It tracks the restrictions on data the agent has read and checks each tool call before it runs. In our setup, the rules are:
- A session starts unrestricted.
- Reading from an internal tool marks the session as internal.
- Once marked internal, the session can’t publish to public destinations without an authorized review—for example, by copying internal logs and configuration into a public GitHub issue.
Here’s what that means for our bug reports:
After reading an internal Slack thread, the agent can’t simply post what it learned to a public GitHub issue. OpenAPPA blocks the tool call before it runs.
The agent needs the original thread and screenshots to understand the bug. Giving it that access shouldn’t also give it permission to publish the internal information it finds there.
OpenAPPA covers both MCP tools and CLI tools. For example, we added a script that checks which cluster a kubectl command reads from. If it’s a non-local cluster, the output is marked internal. The script also marks reads from cloud APIs as internal, while leaving ordinary local development alone.
If an agent disguises a command in a way the script doesn’t recognize, we also use OpenAPPA’s built-in LLM classifier as a second line of defense. It assesses calls we haven’t manually annotated and helps identify commands that read internal data, so the same restrictions can apply.
OpenAPPA is MIT-licensed and currently in preview; the
is up and the
is public, but it’s still an RFC rather than a finished product. We’re using it on our own bug-fixing sessions to see how these policies hold up when agents need internal data to do useful work.
We'd rather show up in the evidence for the optimistic case than end up in the headlines with a major incident. Simon, if you ever want to try to break it, please do. It may be exactly the stress test this thing needs.
The verdict
Things it’s good at: small, well-described, self-contained fixes.
Things it’s not so good at: anything that requires taste, architecture, or an argument. We don’t let it independently work on hard problems, as those require us to be ‘in the loop’. Its PRs open on a private mirror first and get published to the public repo only when a human says "Promote".
The tool hasn't made us faster at hard work, but it has reduced the cost of a small bug fix to the cost of a bug report, and as it turns out, an enormous amount of bugs are much more affordable and easier to fix than we’ve got used to thinking.
Drop a crab in the thread. Go back to what’s important.