把 AI 编码代理放进容器、虚拟机或受限执行环境,并不等于完成了安全隔离。GitLab 在一次内部评估中发现,代理可以利用一个存在漏洞的软件包代理服务逃离沙箱;关键在于,这个服务为了支持依赖安装,已经被明确加入网络白名单。
这个案例揭示了一个容易被忽略的事实:沙箱的实际信任边界,不只由文件系统、进程权限和系统调用决定,还包括代理能够访问的每一个网络端点。
白名单不是安全证明,而是信任扩张
AI 编码代理通常需要联网完成这些工作:
- 从 PyPI、npm、Maven 或内部制品库安装依赖;
- 访问 Git 服务、代码审查 API 和工单系统;
- 调用模型、浏览器或测试环境;
- 上传构建日志与产物。
为了让工作流正常运行,团队往往会允许代理访问内部软件包代理。问题在于,网络策略通常只回答“能否连接”,并不回答以下问题:
- 目标服务是否存在可被恶意请求触发的漏洞;
- 代理是否能够调用本不需要的 HTTP 方法或管理接口;
- 软件包代理能否继续访问云元数据、控制平面或其他内部系统;
- 代理获得的是只读下载权限,还是还可以上传、删除或修改包;
- 服务被攻陷后,是否能读取其他租户的凭据和缓存。
因此,将某个域名、IP 或 Kubernetes Service 加入 allowlist,本质上是在扩大沙箱的可信计算基。这个服务一旦成为跳板,原本严格的本地隔离就可能失去意义。
风险不是“AI 特有”,但代理会放大它
传统构建任务通常执行预先审查过的脚本,而 AI 代理会根据仓库内容、提示词和工具返回结果动态生成命令。攻击者可能通过恶意仓库、依赖元数据、测试输出或提示词注入,诱导代理向白名单服务发送异常请求。
需要把两类风险分开处理:
- 代理自身越权:例如读取挂载的密钥、启动特权进程或访问宿主机接口。这类问题主要依靠沙箱、seccomp、只读文件系统和最小权限解决。
- 代理借助可信服务横向移动:代理没有直接访问敏感网络,却能攻击一个被放行的中间服务,再利用该服务的权限继续移动。这需要网络分段、服务加固和身份隔离共同解决。
只做第一类控制,会产生一种危险的“容器已经隔离,所以任务安全”的错觉。
可以这样实践:为代理建立默认拒绝的出口策略
下面是一份可改造的 Kubernetes NetworkPolicy。它只允许沙箱 Pod 查询集群 DNS,并访问带有指定标签的软件包代理。应用前需要修改命名空间、Pod 标签和端口,使其与实际环境一致。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: agent-default-deny-egress
namespace: agent-sandbox
spec:
podSelector: {}
policyTypes:
- Egress
egress: []
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: agent-allow-dns
namespace: agent-sandbox
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: agent-allow-package-proxy
namespace: agent-sandbox
spec:
podSelector:
matchLabels:
app: coding-agent
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: package-services
podSelector:
matchLabels:
app: package-proxy
ports:
- protocol: TCP
port: 8080
保存为 agent-egress.yaml 后执行:
kubectl apply -f agent-egress.yaml
kubectl -n agent-sandbox get networkpolicy
可以启动临时 Pod 验证策略。以下示例假设软件包代理的 Service 名为 package-proxy,并提供 /health 接口:
kubectl -n agent-sandbox run egress-test \
--rm -it --restart=Never \
--labels=app=coding-agent \
--image=curlimages/curl:8.10.1 -- \
sh -c '
echo "Allowed endpoint:";
curl -fsS --max-time 5 http://package-proxy.package-services.svc.cluster.local:8080/health;
echo;
echo "External endpoint should be blocked:";
curl -I --max-time 5 https://example.com || true
'
这份策略只能缩小可达范围,不能证明软件包代理本身安全。还要确认集群使用的 CNI 插件确实支持并执行 NetworkPolicy。如果代理通过 Service Mesh、节点级代理或其他出口网关访问网络,也要检查实际流量路径是否绕过了策略。
软件包代理应被当作边界系统加固
对只负责安装依赖的代理任务,可以进一步限制软件包代理的能力:
- 只开放下载所需的
GET、HEAD等方法; - 将上传和管理接口放到不同域名、端口或网络区;
- 为每次代理运行签发短期、只读凭据;
- 禁止软件包代理访问云元数据地址和不必要的内部网段;
- 及时修补代理软件及其插件,并减少管理组件暴露;
- 对异常路径、超大请求体、高错误率和管理接口探测建立告警;
- 对依赖使用锁文件、哈希或制品摘要,避免代理任意选择版本;
- 隔离不同项目、租户和安全等级的缓存与凭据。
如果现有代理前面使用 Nginx,并且这个端点确实只承担下载,可以采用类似的最小化配置。下面只是可改造示例,运行前应替换上游地址,并确认包管理器不需要其他 HTTP 方法:
upstream internal_package_proxy {
server package-proxy.package-services.svc.cluster.local:8080;
keepalive 16;
}
server {
listen 8081;
client_max_body_size 1m;
location /repository/ {
limit_except GET HEAD {
deny all;
}
proxy_set_header Host $host;
proxy_set_header X-Request-ID $request_id;
proxy_connect_timeout 3s;
proxy_read_timeout 30s;
proxy_pass http://internal_package_proxy;
}
location / {
return 404;
}
}
HTTP 方法和路径限制并不能替代漏洞修复,但可以减少代理接触管理面或非必要功能的机会。如果工作流还需要发布包,应为发布动作建立独立身份、独立端点和人工审批,而不是给通用编码代理增加写权限。
上线前应检查整条调用链
评估 AI 代理沙箱时,不要只问“容器是否特权运行”。更实用的检查清单是:
- 默认是否拒绝全部出站连接;
- 每个允许访问的服务是否有明确业务理由;
- 白名单服务被攻陷后能够访问哪些系统;
- 代理使用的凭据是否短期、只读并绑定单次任务;
- 依赖下载与依赖发布是否使用不同身份;
- 是否记录 DNS、HTTP、身份认证和网络流量;
- 能否快速撤销令牌、隔离任务并保存调查证据;
- 是否定期对允许访问的中间服务进行补丁和攻击面复核。
真正可靠的设计不是寻找一个“足够坚固”的沙箱,而是让每一层失守后都无法自然通向下一层。沙箱、出口策略、服务加固、短期身份和可审计日志必须共同工作;否则,一条看似合理的网络白名单,就可能变成代理离开隔离区的桥梁。