AI 编程工具能读取项目、修改文件、执行命令,也就天然具备把代码发送到外部服务的能力。九月中旬曝光的 ZCode 事件让风险变得非常具体:工具可能在后台打包整个代码仓库,甚至把 .git 历史一并上传。
针对这类问题,Apache 软件基金会旗下的开源项目 Casbin Gateway 新增了 Agent 外发监测能力。它所解决的并不只是“某个工具是否可信”,而是更基础的治理问题:开发者和企业应当知道 AI Agent 正在向哪里连接、传输了多少数据,以及一次请求是否疑似包含整个代码库。
为什么 .git 外泄比当前源码外泄更严重
一个工作目录中的源码只是仓库当前状态,而 .git 保存的是版本历史。它可能包含:
- 已经从当前分支删除,但仍留在历史提交中的密钥;
- 内部域名、测试地址、用户名和提交者邮箱;
- 尚未发布的功能、回滚记录与安全修复过程;
- 分支、标签、远程仓库地址及团队协作信息;
- 曾经误提交,后来仅通过普通删除移除的配置文件。
因此,外发监测不能只匹配 .env、id_rsa 等少数文件名。更有价值的信号是组合行为:某个 Agent 在短时间内读取大量文件,访问 .git/objects,生成压缩包,随后向一个此前未批准的域名发起大流量请求。
单个信号很容易误报。例如,IDE 索引也会读取整个目录,Git 自身当然会访问 .git。但当“读取范围、目标域名、请求体积、进程身份和时间窗口”同时出现异常时,判断就可靠得多。
网关应该观察什么
把 Agent 流量统一经过本地网关,最大的价值是建立一个稳定的控制点。相比逐个审计插件或等待供应商说明,网关可以从几个维度记录外发行为:
- 进程与 Agent 身份:请求由哪个编辑器、CLI 或子进程产生。
- 目标地址:域名、IP、端口以及是否属于批准的模型服务。
- 传输规模:单次请求和短时间累计上传了多少字节。
- 仓库特征:请求是否疑似携带 Git 对象、文件清单或压缩后的整仓内容。
- 策略结果:允许、告警、阻断,还是要求人工确认。
这里需要区分“发现”和“阻断”。监测模式适合先建立基线,避免突然影响开发流程;涉及私有仓库、生产凭据或受监管数据时,则应考虑默认拒绝,并只允许经过审批的目的地。
HTTPS 也构成明确边界。如果网关只看到加密连接,它通常能观察目的地址、连接时间和流量大小,却未必能判断请求体中具体有哪些文件。若要做内容级检查,需要受控的 TLS 终止、Agent 原生集成或端点侧文件访问审计。证书固定、HTTP/3、QUIC 以及不遵守系统代理设置的客户端,也可能绕过普通转发代理。
动手验证:模拟一次包含 .git 的整仓上传
下面的实验不依赖真实代码仓库,用一个临时 Git 项目和本地 HTTP 接收端模拟整仓外发。它可以用于验证网关是否记录或阻断了这类请求。
先创建测试仓库和压缩包:
set -euo pipefail
LAB_DIR="$(mktemp -d)"
cd "$LAB_DIR"
git init demo-repo
cd demo-repo
git config user.name "Gateway Lab"
git config user.email "gateway-lab@example.test"
printf 'print("hello")\n' > app.py
printf 'LAB_CANARY=do-not-upload\n' > .env.test
git add app.py .env.test
git commit -m "create test repository"
cd ..
tar -czf demo-repo-with-git.tar.gz demo-repo
echo "Lab directory: $LAB_DIR"
echo "Archive size: $(wc -c < demo-repo-with-git.tar.gz) bytes"
tar -tzf demo-repo-with-git.tar.gz | grep -E '(^|/)\.git/' | head
接着在另一个终端启动一个只用于本机测试的接收器:
cat > /tmp/upload_receiver.py <<'PY'
from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
import hashlib
OUTPUT = Path("/tmp/received-upload.bin")
class Handler(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get("Content-Length", "0"))
body = self.rfile.read(length)
OUTPUT.write_bytes(body)
digest = hashlib.sha256(body).hexdigest()
print(f"received={len(body)} sha256={digest} path={self.path}")
self.send_response(204)
self.end_headers()
HTTPServer(("127.0.0.1", 18080), Handler).serve_forever()
PY
python3 /tmp/upload_receiver.py
回到第一个终端执行上传:
cd "$LAB_DIR"
curl --fail --silent --show-error \
-X POST \
-H 'Content-Type: application/gzip' \
--data-binary @demo-repo-with-git.tar.gz \
http://127.0.0.1:18080/upload
sha256sum demo-repo-with-git.tar.gz /tmp/received-upload.bin
两个 SHA-256 值应当一致,说明压缩包被完整发送。接入 Casbin Gateway 后,可以把接收地址换成测试目的地,并根据项目当前文档配置代理或 Agent 接入方式,再观察是否出现以下结果:
- 记录发起请求的 Agent 或进程;
- 记录目标、请求体积和时间;
- 将包含
.git的归档标记为高风险; - 在阻断模式下拒绝请求,同时留下可审计事件。
不要在测试中使用真实仓库、真实密钥或生产服务。部分 Agent 不读取 HTTP_PROXY、HTTPS_PROXY 环境变量,另一些会直接使用 QUIC;验证时应同时检查操作系统连接,避免误以为流量已经进入网关:
sudo ss -tpn
sudo lsof -nP -iTCP -sTCP:ESTABLISHED
如果仍有 Agent 直连外网,仅部署代理并不够,还需要通过主机防火墙或容器网络限制直连路径。
策略不要只写成“禁止上传代码”
“禁止上传代码”既难以执行,也会让依赖云端模型的 Agent 完全失去作用。更可操作的规则应描述主体、目的地、数据范围和动作。下面是一个策略设计示意,字段名并不代表 Casbin Gateway 当前版本的固定配置格式,落地时需要按项目实际文档改写:
# 策略模型示意:不是 Casbin Gateway 固定配置格式
egress_policy:
default_action: deny
approved_destinations:
- host: model-api.example.internal
agents:
- approved-editor-agent
max_request_bytes: 1048576
sensitive_paths:
- ".git/**"
- "**/.env"
- "**/*.pem"
- "**/*secret*"
detections:
- name: possible-full-repository-upload
conditions:
minimum_files_read: 200
upload_bytes_over: 5242880
window_seconds: 60
action: require_confirmation
- name: git-history-egress
conditions:
path_matches: ".git/**"
action: deny
真实环境中还应为依赖下载、Git 托管平台和内部模型服务分别制定规则。git push 本身会传输 Git 对象,不能仅凭看到 .git 特征就一律阻断;应结合目标是否为批准的 Git 服务、当前进程是否为 Git,以及用户是否主动触发操作。
上线时采用“观察—收紧—阻断”
比较稳妥的接入方式不是第一天就阻断全部未知流量,而是分阶段推进:
- 观察阶段:记录 Agent、域名、端口、上传大小和命中规则,不改变请求结果。
- 基线阶段:整理合法模型 API、代码托管服务与插件更新地址,删除不必要的例外。
- 告警阶段:对大体积 POST、未知目的地、后台静默上传以及
.git访问组合发出告警。 - 阻断阶段:拒绝未批准目的地、凭据文件、Git 历史和超过阈值的批量外发。
- 持续验证:每次 Agent 或网关升级后,重复执行可控的整仓上传测试。
Casbin Gateway 这类控制面的意义,在于把“相信工具不会上传”转化为“能够看见、记录并限制工具上传”。不过网关不是唯一防线:最小权限工作区、短期凭据、仓库密钥扫描、网络出口限制和 Agent 白名单仍然需要同时存在。对于能读取完整代码库并执行命令的 AI Agent,应当按本地高权限程序治理,而不是按普通聊天网页对待。