如果你在其他平台看到这篇文章,这可能不是最终版本。为了获得更好的阅读体验(包含最新的评论讨论和勘误),欢迎移步原文

写在前面

作为一个折腾爱好者,最近在弄让 AI Agent 自动跑项目、提 PR 的工作流。结果一开始没做频率限制,专门给 Agent 用的 GitHub 小号用裸的 gh CLI 短时间内狂刷 PR 和评论,直接被 GitHub 的防滥用风控当场封禁(Suspended)。

痛定思痛后,我决定引入 MCP(Model Context Protocol)来接管 GitHub 操作,因为官方 MCP 自带了完备的限流和退避机制。但同时,为了让 Agent 还能正常执行 git push,我依然在容器的环境变量里留了一个 PAT(Personal Access Token)。

结果在观察日志时,我发现了一个更危险的现象:大模型一旦发现 MCP 工具调用失败或者图省事,它就会直接去翻环境变量里的 PAT,然后自己手搓 curl 脚本去直连 GitHub API!而且它的脚本一旦报错,它为了修 Bug 就会无脑高频重试,眼看着又要因为频繁发包触发一次风控。

这让我意识到一个严峻的底层问题:只要 PAT 还在容器里,Agent 迟早会绕过你的防护机制。

这篇文章纯分享一下,我最后是如何利用上一篇文章里提到的 VS Code “坑点”,配合 MCP 与网络代理,彻底实现容器内凭证隔离的。

防封的核心痛点:如何隔离 PAT?

我的诉求其实很简单:

  • API 操作:必须强制走 MCP Server,不仅能规范请求格式,后续也方便在网关层做全局限流。
  • Git 操作:Agent 仍然需要执行基础的 git clonegit push

这里就产生了一个悖论:Git 推送必定需要身份认证。如果不把 PAT 放进容器,Git 就推不上去;如果把 PAT 放进容器,Agent 又会拿去跑 curl 惹祸。

为了解开这个死结,我一开始梳理了四条解决路径:

  • 方案一:在网关层做手脚
    • 专门设置一个内网代理,给 git push 相关的请求自动注入 Auth Header。但处理 HTTPS 流量需要解密,配置成本太高。
  • 方案二:降级并隔离 Token
    • 给 Git 专门申请一个只有代码读写权限的细粒度 Token。这样即便 Agent 拿去调 API 也干不了什么坏事。但这依然没有彻底解决凭证落盘的问题。
  • 方案三:全部 MCP 化
    • 结合前两种思路,干脆自己搓一个专门用来调度 Git 操作的 MCP Server,让容器里连 Git 凭证都不留。但这个方案过重了。
  • 方案四:使用 GitHub App
    • 这是个相对靠谱的新思路:用 GitHub App 注册一个 Bot 账号。这样个人 PAT 就彻底解绑了,App 自带的一套流控和权限体系,不用太担心连累大号。

这四个方案虽然都能走通,但各有各的别扭。正当我准备挑一个硬着头皮落地时,没想到过了两天,我在日常开发中意外踩了另外一个大坑(详见这篇排障记录:我的 Github 凭证怎么总失效?原来是 VSCode 搞的鬼)反而让我因祸得福,找到了堪称完美的“第五种解法”。

最终解法:把 VS Code 的“坑”变成特性

在那次排障中我发现,当我们使用 Attach to Running Container 进入容器开发时,VS Code 会在背景偷偷注入一个基于 IPC 桥接的 git credential helper

当时我把它当成一个干扰认证的“深坑”,但转念一想,把它放在当前的防封沙盒场景下,这不就是现成的无凭证隔离神器吗!

因为这个机制的存在,整个工作流迎刃而解:

  • 容器内部:不需要存储任何 PAT,也没有任何相关的环境变量凭证。
  • Git 推送:当 Agent 在终端执行 git push 时,Git 会自动调用 VS Code 注入的 helper,通过 IPC 通道向宿主机索要凭证并完成推送(Agent 对此过程无感知)。
  • API 调用:如果 Agent 企图在终端里手写 curl api.github.com,由于它根本找不到 PAT,请求会直接报鉴权失败。所有合法的 API 操作只能乖乖通过局域网,去调用外部持有 PAT 的 MCP Server。

这样一来,凭证不仅彻底从容器中剥离,而且完全没有增加额外的开发成本。

官方 MCP 的坑:如何支持容器调用?

既然剥离了容器内部的凭证,我们就需要把 MCP Server 作为一个独立的服务跑起来。

但如果你直接用 GitHub 官方的 MCP 库(@modelcontextprotocol/server-github),会踩到一个坑:它默认只支持 Stdio(标准输入输出),不支持作为 HTTP 服务被其他容器调用。

我们需要引入 mcp-proxy 作为中间层,将 HTTP SSE(Server-Sent Events)请求转化为标准输入输出。

编写一个自定义的 Dockerfile

# 使用轻量级的 Node 镜像
FROM node:22-alpine

# 全局安装网络代理工具 mcp-proxy
RUN npm install -g mcp-proxy

# 声明对外暴露的端口
EXPOSE 8080

# 核心魔法:让 mcp-proxy 监听 8080 端口,包装并启动 GitHub 官方 server
CMD ["mcp-proxy", "--port", "8080", "--", "npx", "-y", "@modelcontextprotocol/server-github"]

极简编排:连代理都不需要了

以前为了防 Agent 乱飞请求,我还要专门搭一个 Squid 出网代理(Egress Proxy),用白名单模式把 api.github.com 给物理墙掉。

但既然现在凭证已经被 VS Code 抽离到了宿主机,这个沉重的网络沙盒也就可以直接扔了。因为就算你给 Agent 完全畅通的公网,只要它手里没有 PAT,去直连 API 撞大门也只会收到 401 Unauthorized,根本连触发账号级风控的资格都没有。

现在的架构变得极致清爽,只需要保留两部分:“无凭证的业务 Agent” 和 “持有凭证的 MCP 网关”。

完整的 docker-compose.yml 如下:

version: '3.8'

services:
# 1. 负责执行 API 操作的 MCP 网关(持有凭证)
mcp-gateway:
build: ./mcp-server
ports:
- "8080:8080"
environment:
# 只有这里存有真实的细粒度 PAT
- GITHUB_PERSONAL_ACCESS_TOKEN=github_pat_xxxxxx

# 2. Agent(无凭证的轻量沙盒)
agent:
image: your-agent-image:latest
environment:
# 告诉 Agent,合法的 GitHub API 请求必须走这个网关
- MCP_SERVER_URL=http://mcp-gateway:8080/sse
depends_on:
- mcp-gateway

补充预想方案:把“上网”也变成 MCP 工具
后面我打算连常规的公网直连权限也给 Agent 收回,单独提供一个带有 search_webfetch_url 工具的 MCP Server。服务端可以把网页清洗成干净的 Markdown 再喂给大模型。不仅节省 Token,还能从根本上杜绝任何意料之外的网络请求。

效果验证

服务跑起来后,你可以亲自进容器模拟一下大模型的行为,看看效果有多棒:

  • 合规拉包 / 推代码:执行 npm install 畅通无阻;执行 git push,VS Code 的 IPC 机制默默发力,宿主机提供凭证,推送成功。整个过程 Agent 对凭证的存在一无所知。
  • 违规企图绕开 MCP 调 API:执行 curl -I https://api.github.com/user。由于没有携带 Token,瞬间返回 HTTP/2 401 Unauthorized。GitHub 根本不知道发起请求的是谁,自然也无从封禁你的大号/小号。
  • 合规使用 MCP 调 API:Agent 撞墙发现自己没权限后,向 http://mcp-gateway:8080/sse 发送调用请求。MCP 网关从容接管,带着合法的 PAT 代为请求,并自动执行严谨的频率控制与退避策略。

总结

其实这一圈折腾下来,最大的收获反而是底层的认知转变:

千万别把 Agent 当成一个懂规矩的同事。只要给了它带有敏感权限的通用终端环境,它其实就是个随时可能写出死循环的“自动化脚本执行器”。

不要试图用 Prompt 去“劝它向善”(比如写上“严禁使用 curl”)。真正有效的防御,是用网络隔离收紧危险操作的物理口子,把敏感凭证托付给宿主机机制,并强制一切越权操作走 MCP 协议进行统一收口。