AI 系统正在从“调用一个模型”变成“由多个模型、多个 Agent Harness、多个工具共同完成任务”。模型可能来自不同供应商,Harness 可能负责规划、记忆、重试、工具调用或人工审批,而真正产生副作用的动作则发生在数据库、云平台和企业内部系统中。
这种组合带来了一个经典但容易被忽视的问题:混淆代理(confused deputy)。一个程序本来只是替用户完成操作,却使用了自己更高的权限,替用户访问了不该访问的数据,或执行了用户无权执行的动作。多模型、多 Harness 的世界需要的不只是模型评测和提示词管理,还需要一套位于 Harness 之下的信任模型。
为什么“模型安全”不等于“Agent 安全”
在单一模型调用中,权限边界往往比较直观:应用服务器拿着一个 API Key,调用模型,再把结果返回给用户。但 Agent 会把一次请求拆成多个步骤:读取上下文、选择模型、调用工具、重试失败操作、把结果交给另一个 Agent,甚至根据中间结果继续执行。
风险因此从“模型会不会说错话”扩展成了几个更具体的问题:
- 这次工具调用究竟代表哪个用户?
- 当前模型是否真的被授权执行这个动作,还是只是被 Harness 赋予了能力?
- 一个 Agent 产生的凭证或上下文,能否被另一个 Harness 继续使用?
- 重试、异步队列和人工审批之后,原始授权是否仍然有效?
- 工具返回的数据是否改变了后续步骤的权限判断?
如果系统只检查“调用方是我们的 Agent 服务”,那么所有用户最终都可能共享同一组高权限。此时 Agent 就不再只是替用户办事,而是拿着系统的权限替用户办事,这正是混淆代理问题在新架构中的表现。
信任边界应该放在 Harness 之下
多模型、多 Harness 的现实意味着,不能把安全边界绑定在某一个模型供应商或某一个 Agent 框架上。模型可以替换,Harness 可以重写,但以下信息应该由更稳定的控制层负责验证:
- 主体是谁:用户、服务账号、工作流、模型或工具,不应混为一个身份。
- 授权做什么:权限应描述具体动作和资源,例如“读取某项目的构建日志”,而不是笼统的“可以使用云 API”。
- 授权为什么成立:需要保留用户意图、请求来源、审批记录和策略版本。
- 授权何时失效:短期令牌、单次操作令牌和资源范围限制,比长期共享密钥更容易控制风险。
- 谁实际执行:最终接触数据库、支付接口或生产集群的工具服务,必须再次检查授权,而不是信任上游 Agent 的自我声明。
可以把 Harness 看成“决策和编排层”,而不是“最终信任根”。它可以建议调用哪个模型、怎样拆分任务、何时重试;但它不应该单方面决定用户拥有什么权限。
一个可落地的最小授权网关
下面的 Python 示例展示一种简化做法:Agent 不能直接调用高风险工具,而是把用户身份、意图、资源和动作交给授权网关。网关验证短期令牌,并只允许令牌中明确声明的操作。
这是一个可运行的演示,不代表完整的生产级认证系统。实际部署时应使用成熟的 OAuth 2.0/OIDC、签名令牌库、密钥轮换、审计存储和网络隔离机制。
# policy_gateway.py
from datetime import datetime, timezone
from typing import Dict, Any
# 演示用数据。生产环境不要把令牌和策略硬编码在源码中。
TOKENS = {
"demo-user-token": {
"subject": "user:alice",
"expires_at": 4102444800, # 2100-01-01,仅为演示
"permissions": [
{"action": "build.read_logs", "resource": "project:demo"}
],
}
}
def is_allowed(token: str, action: str, resource: str) -> bool:
grant = TOKENS.get(token)
if not grant:
return False
now = datetime.now(timezone.utc).timestamp()
if now >= grant["expires_at"]:
return False
return any(
item["action"] == action and item["resource"] == resource
for item in grant["permissions"]
)
def execute_tool(request: Dict[str, Any]) -> Dict[str, Any]:
required = {"token", "action", "resource", "request_id"}
missing = required - request.keys()
if missing:
raise ValueError(f"missing fields: {sorted(missing)}")
if not is_allowed(request["token"], request["action"], request["resource"]):
raise PermissionError("denied by policy gateway")
# 这里才进入真正的工具调用;生产环境应记录完整审计事件。
return {
"request_id": request["request_id"],
"status": "executed",
"action": request["action"],
"resource": request["resource"],
}
if __name__ == "__main__":
request = {
"token": "demo-user-token",
"action": "build.read_logs",
"resource": "project:demo",
"request_id": "req-001",
}
print(execute_tool(request))
运行方式:
python policy_gateway.py
这个示例的关键不在于 Python 字典本身,而在于调用路径:模型输出不能直接等价于授权,Harness 传来的身份声明也不能直接等价于用户身份,工具服务必须在副作用发生前重新做策略判断。
多 Harness 协作时,哪些数据不能“顺手传递”
当一个 Harness 把任务交给另一个 Harness 时,最容易出现的是上下文和权限混用。建议把以下对象分开处理:
- 用户意图:例如“帮我查看项目 demo 的构建日志”。它是待解释的输入,不是权限本身。
- 授权凭证:由可信授权服务签发,包含主体、动作、资源、过期时间和唯一请求标识。
- 模型上下文:包括提示词、检索内容和工具结果,可能被污染,不能单独用于授权。
- 执行结果:工具返回的内容可以进入下一步推理,但不能自动扩大下一步权限。
一种实用的约束是:下游 Harness 只接收经过裁剪的任务令牌,而不是上游服务的完整访问令牌。每次跨边界传递都生成新的关联 ID,并保留原始主体和授权链,方便审计和撤销。
对于删除、付款、发布生产配置等高影响动作,还可以加入二次确认或人工审批。审批应绑定具体资源、参数摘要和有效期,而不是笼统批准“让这个 Agent 继续工作”。
落地检查清单
可以按下面的顺序改造现有 Agent 系统:
- 为用户、Harness、模型和工具建立不同的身份类型。
- 禁止 Agent 直接持有长期云密钥或数据库超级用户凭证。
- 为每次工具调用携带明确的主体、动作、资源、用途和过期时间。
- 在真正执行副作用的服务端再次进行授权检查。
- 将提示词、模型输出和检索内容视为不可信输入。
- 为重试、异步任务和跨 Harness 委派保留请求关联 ID。
- 记录“谁代表谁、在什么策略版本下、执行了什么动作”。
- 对高风险动作设置最小权限、幂等键、限额和人工确认。
- 定期测试混淆代理场景:越权读取、跨租户访问、令牌转交、过期授权重放。
多模型、多 Harness 并不意味着必须依赖某个单一框架来解决信任问题。更稳妥的做法,是把信任放到模型和编排器之下,把授权收敛到独立、可审计、可撤销的执行边界。这样即使更换模型、重构 Harness,系统仍然能够回答最重要的问题:这次动作是谁授权的,允许作用于什么资源,以及为什么现在可以执行。