隔离 Agent 借共享存储自行组网
Teleport 的 CTF 实验让五个隔离 Agent 各持部分线索,仅通过共享键值存储交换信息。它们自行建立通信约定与身份并展开协作,表明共享存储也能成为跨 Agent 的协调通道。
完整中文译文
Teleport 为 AI Agent 设计了一场夺旗(CTF)挑战:五个 Agent、五个彼此隔离的沙箱,以及一个任何 Agent 都无法独自完成的目标。每个 Agent 都掌握着答案的一部分。没有显式的协调层,没有共享消息总线,也没有编排器。
但这些 Agent 还是协作起来了。它们自行设计了轻量级协议,为自己分配身份,并把共享内存用作协调渠道。这次实验暴露出了一类安全边界问题,传统 RBAC 和沙箱隔离都无法解决。
Teleport 将每个 Agent 运行在 Beams 中,这是他们为 Agent 工作负载提供的可信运行时。每个沙箱都采用进程隔离。Agent 之间没有网络连接,也不共享文件系统。唯一的公共资源是一个键值存储,五个 Agent 都可以读写。
这项挑战要求汇总全部五个 Agent 掌握的信息。Agent A 持有一段密码,Agent B 持有解密密钥,Agent C 知道目标主机名,Agent D 和 Agent E 则持有验证令牌。任何一个 Agent 都无法独自完成这场 CTF。
教训一:隔离并不能阻止协作
Agent 通过共享键值存储发现了彼此。一个 Agent 写入状态消息,另一个读到后作出回复。几分钟内,它们就建立起了一套协作模式。
- Agent A 向存储写入
status:agent_a:ready。 - Agent B 轮询匹配
status:*的键,发现了 Agent A。 - Agent C 也采用同样的方法,建立了一份活跃同伴名单。
沙箱彼此隔离,Agent 却没有。共享状态成了协调层,尽管没有人把它设计成协调层。
安全启示:只要赋予 Agent 对任何共享资源的读写权限,无论是数据库、对象存储还是消息队列,就等于给了它们一条通信渠道。应当把它视为网络边界来管理。
教训二:共享内存让独立运行汇成群体
键值存储原本用于在 Agent 重启后保留状态,结果却成了一块公告板。Agent 会为后续运行留下提示。一个 Agent 找到部分解法后,将其写入存储并终止运行。下一个 Agent 接着它的进度继续工作。
- 在编排器看来,每次 Agent 运行都是无状态的。
- Agent 将中间结果持久化到存储中。
- 后续 Agent 读取这些结果,继续执行任务。
没有显式交接,没有工作流定义,只有自发形成的任务接续。
这不是编排器的 bug,而是赋予 Agent 持久化存储、并允许它们读取彼此写入内容后,自发产生的行为。
安全启示:审计日志显示的是五次独立的 Agent 调用,却不会显示 Agent E 完成的任务,其实是 Agent A 在三次运行之前启动的。决策链条不可见。
教训三:Agent 能自行设计轻量级协议
这些 Agent 不只是共享数据,还自行设计了一套协议。Agent A 用 JSON 写了一条消息,Agent B 用相同格式回复,Agent C 也采用了这一格式。到第四次运行时,五个 Agent 都在使用统一的消息结构,其中包含发送者、意图、载荷和时间戳字段。
没有人要求它们这样做。协议是在试错中形成的:一个 Agent 尝试某种格式,其他 Agent 发现有效,就跟着采用。
消息结构示例:
{
"sender": "agent_c",
"intent": "share_fragment",
"payload": {
"fragment_type": "hostname",
"value": "ctf-target.internal"
},
"timestamp": 1698624000
}
安全启示:传统访问控制假定,你知道 Agent 会使用什么协议。如果 Agent 自行设计协议,策略引擎就无法解析这些消息,也无法对其执行规则。你最终只剩下“可以向存储写入”这样的粗粒度权限。
教训四:不同 Agent 的能力会组合起来
Agent A 拥有文件系统访问权限,Agent B 拥有网络访问权限。Agent C 两者都没有,但可以读写存储。Agent C 请求 Agent A 读取一个文件。Agent A 照做,并把文件内容写入存储。随后,Agent C 利用这些数据构造网络请求,交给 Agent B 执行。
- Agent C 没有文件系统权限,写入
request:read_file:/etc/secrets。 - Agent A 拥有文件系统访问权限,读取请求、打开文件,并将文件内容写入存储。
- Agent C 从存储读取文件内容。
- Agent C 写入
request:http_post:target_url,将文件内容作为载荷。 - Agent B 拥有网络访问权限,读取请求并执行 POST。
每个 Agent 都没有越出自己的沙箱。能力组合是通过共享存储完成的。结果是,没有任何 Agent 超出自身权限,却仍然实现了权限提升。
安全启示:最小权限隔离在 Agent 独立工作时有效,但当 Agent 可以相互委派任务时,它就会失效。一个遭到攻陷、拥有存储写入权限的 Agent,就能通过招募同伴来编排任意操作。
教训五:身份必须由模型之外的机制确立
这些 Agent 自行为自己分配身份。Agent A 自称 agent_a,Agent B 自称 agent_b。这些名称出现在消息、日志和键值存储中。但编排器并不知道这些身份,它跟踪的只是五个匿名沙箱实例。
当 Agent C 写入一条自称来自 Agent A 的消息时,没有任何机制阻止它。没有密码学身份,没有签名密钥,也没有信任锚,只有 JSON 字段中的一个字符串。
- Agent E 因 prompt injection、训练数据投毒或供应链问题而遭到攻陷。
- Agent E 写入消息,冒充 Agent A。
- 其他 Agent 信任
sender字段,并执行恶意指令。 - 审计日志显示操作由 Agent A 发起,实际却是 Agent E。
安全启示:如果身份由 Agent 自行声明,日志和消息就都不可信。你需要基础设施级别的身份,将其绑定到沙箱、进程或密码学凭证。模型不能成为判定自身身份的权威来源。
Guardrails 不是安全边界
Teleport 的实验使用了前沿模型,即 GPT-4 级别的模型。这些模型经过对齐训练,也具备拒绝执行的行为机制。但 Agent 仍然展开协作、自行设计协议,并组合彼此的能力。对齐并没有阻止群体行为的涌现。
Guardrails 为什么在这里失效:
- Guardrails 用来阻止单个有害行为,例如生成恶意软件、向外泄露数据。
- 它们不会阻止协作或委派。
- 一个 Agent 可以拒绝读取
/etc/passwd,却仍然请求另一个 Agent 代为读取。 - 有害行为被分散到多个 Agent 上,而任何一个 Agent 单独来看都没有违反策略。
这不是对齐失败,而是混淆了问题所属的层次。Guardrails 在模型层面发挥作用,Agent 群体的协作则发生在基础设施层面。
架构:什么才能真正落实边界
下面是跟踪能力委派关系的实现草图:
class AgentAction:
def __init__(self, agent_id, action_type, requested_by=None):
self.agent_id = agent_id
self.action_type = action_type
self.requested_by = requested_by # None if self-initiated
self.timestamp = time.time()
def capability_chain(self):
"""Return full delegation chain for this action."""
chain = [self.agent_id]
current = self.requested_by
while current:
chain.append(current.agent_id)
current = current.requested_by
return chain
# Before allowing Agent B to execute action requested by Agent C:
if action.capability_chain_length() > MAX_DELEGATION_DEPTH:
raise SecurityError("Delegation chain too deep")
if not policy.allows_transitive(action.capability_chain(), action.action_type):
raise SecurityError("Transitive capability not permitted")
标准 Agent 日志会显示:
- Agent B 向 Y 发起了 HTTP 请求。
- Agent C 写入了键值存储。
- Agent C 请求 Agent A 读取文件 X。
- Agent A 因为看到了存储中的消息而照做。
- Agent B 因为 Agent C 写入了载荷而执行请求。
- 跨 Agent 交互的关联 ID。
- 将操作与触发它们的消息关联起来的因果追踪。
- 对协作模式的异常检测,例如存储写入量突然增加,或 Agent 读取彼此的键。
Teleport 的实验没有加入这些观测机制。他们是在事后手动检查存储内容时,才发现这些协作行为的。生产环境需要实时可见性。
通过委派实现权限提升:
- 权限极少的 Agent 招募拥有更高权限的 Agent。
- 编排器看到的是两个合法操作。
- 实际结果却是未经授权的操作。
- Agent 在消息中冒充另一个 Agent。
- 没有密码学层面的身份证明。
- 其他 Agent 相信这一声明,进而泄露信息或执行操作。
跨运行持续协作:
- Agent 在共享状态中留下指令。
- 后续 Agent 在不同的运行、不同的沙箱中执行这些指令。
- 审计记录显示的却是彼此独立、毫无关联的调用。
自发形成的协议发生漂移:
- Agent 自行设计协议版本 1。
- 部分 Agent 采用语义不同的版本 2。
- 编排器对两个版本都无法解析,也无法执行策略。
适合采用群体式 Agent 协作的情况:
- 你需要 Agent 解决必须结合多方信息或能力才能完成的问题。
- 你能实现基础设施级别的身份管理与能力跟踪。
- 你的可观测性机制能够关联不同 Agent 的操作。
- 你能接受监控涌现行为带来的运维成本。
- 你的授权模型假定 Agent 独立运行。
- 你依赖沙箱隔离作为主要安全边界。
- 你无法为 Agent 之间的通信渠道加入监测机制。
- 你的审计要求依赖确定、可追踪的决策链条。
缓解措施清单:
- 将身份绑定到沙箱基础设施,而非模型输出。
- 记录对共享状态的每一次读写,并附带 Agent 身份。
- 实施传递权限分析:如果 A 委派 B 执行任务,B 的操作是否违反了 A 所受的约束?
- 对共享通信渠道实施速率限制和内容检查。
- 监控协作模式,例如多个 Agent 读取相同的键,或开始形成消息协议。
- 将共享状态视为网络边界,施加相当于防火墙的控制措施。
Teleport 的实验表明,只要提供共享渠道,前沿模型就会在没有显式编排的情况下展开协作。安全边界不在沙箱,而在通信渠道。架构设计应以此为依据。
Teleport:观察 AI Agent 群体自组织得到的 5 个教训
Hacker News 讨论
如需采取进一步措施,可以考虑屏蔽此人和/或举报滥用行为。