当 AI 智能体能够读取邮件、调用 API、修改工单、执行脚本并访问内部数据时,安全问题就不再只是“模型会不会输出危险内容”。OpenAI 与 Hugging Face 相关事件中暴露出的 17,600 次攻击动作,说明攻击者可以持续试探、组合和放大智能体的权限边界。
这类系统不能依赖人工逐条审核。真正有效的方案,需要同时约束智能体能做什么、观察它实际做了什么,并在风险变化时自动收紧权限。
为什么人工审核跟不上智能体
传统软件的危险操作通常有明确入口:一次数据库写入、一次生产发布,或者一次权限变更。智能体的行为则可能由几十轮推理和工具调用组成。单个动作看起来无害,组合起来却可能完成数据外传、权限提升或供应链攻击。
攻击者也不需要一次成功。他们可以反复提交提示词、伪造上下文、诱导工具调用,观察系统响应,再根据结果调整下一次行动。17,600 次动作的意义,不只是数量大,而是它揭示了一个事实:当攻击速度远高于人工审查速度时,人工只能覆盖少量高风险节点。
因此,安全边界必须从“人是否看过这次请求”转向可执行的系统控制:
- 限制智能体可以访问的资源和工具。
- 对每次工具调用记录身份、参数、目标和结果。
- 根据动作链、资源敏感度和异常频率动态评估风险。
- 对高风险操作执行拒绝、暂停或升级审批。
- 让凭证具备最小权限、短生命周期和明确的作用域。
三层控制:约束、观测、治理
1. 约束:把权限边界写进系统
不要只在系统提示词中告诉模型“不要删除数据”。提示词不是权限系统。真正的约束应该由 API 网关、工具代理、沙箱和身份系统执行。
例如,一个工单智能体可以拥有读取工单和添加评论的权限,但不应直接拥有删除工单、导出全部客户数据或修改账户权限的能力。即使模型被诱导,这些操作也应该在工具层被拒绝。
权限设计可以从以下问题开始:
- 这个智能体是否真的需要该工具?
- 工具是否能限制到单个租户、项目或资源?
- 参数是否有允许范围,例如金额上限和文件路径前缀?
- 操作是否应该使用一次性凭证?
- 失败或超时后,系统是否会自动重试并放大影响?
2. 观测:记录动作链,而不只是最终答案
只记录最终回复无法解释智能体为什么访问了某个系统。审计日志至少应包含:会话 ID、智能体身份、用户身份、工具名称、参数摘要、目标资源、策略决定、结果状态和时间戳。
动作链比单次调用更有价值。例如,连续读取大量客户记录、压缩数据、访问外部上传接口,这些步骤分别看起来可能合理,但组合后就是明显的外传信号。
日志还要能支持实时告警和事后取证。对敏感参数进行脱敏时,不能把资源 ID、目标域名、金额等风险判断所需字段全部删掉。
3. 治理:让策略能随风险变化
治理不是上线前的一次审批。模型、工具、数据源和攻击方式都会变化,策略需要能够版本化、测试和回滚。
一个可操作的治理流程包括:
- 为每类智能体定义允许的工具和资源范围。
- 为高风险工具设置参数级规则和速率限制。
- 使用攻击模拟和回放日志测试策略覆盖率。
- 对异常动作自动暂停会话或撤销临时凭证。
- 保留人工审批,但只把它放在真正需要判断的高风险节点。
一个可运行的工具调用策略示例
下面的 Python 示例演示一个很小的策略引擎。它不是生产级授权系统,但可以作为工具代理或 API 网关规则的起点:对工具、资源范围、金额上限和调用频率进行检查,并输出可审计的决策结果。
运行前无需安装第三方依赖,保存为 agent_policy.py 后执行 python agent_policy.py。生产环境应将规则迁移到集中式策略服务,并使用真实的身份认证、租户隔离和不可篡改日志。
from dataclasses import dataclass
from time import time
@dataclass
class Action:
agent: str
tool: str
resource: str
amount: int = 0
POLICY = {
"support-agent": {
"tools": {"ticket.read", "ticket.comment"},
"resource_prefix": "tenant/acme/ticket/",
"max_calls_per_minute": 30,
}
}
recent_calls = {}
def authorize(action: Action) -> dict:
policy = POLICY.get(action.agent)
if not policy:
return {"allow": False, "reason": "unknown_agent"}
if action.tool not in policy["tools"]:
return {"allow": False, "reason": "tool_not_allowed"}
if not action.resource.startswith(policy["resource_prefix"]):
return {"allow": False, "reason": "resource_out_of_scope"}
if action.amount > 100:
return {"allow": False, "reason": "amount_exceeds_limit"}
now = time()
calls = [t for t in recent_calls.get(action.agent, []) if now - t < 60]
if len(calls) >= policy["max_calls_per_minute"]:
return {"allow": False, "reason": "rate_limit_exceeded"}
calls.append(now)
recent_calls[action.agent] = calls
return {"allow": True, "reason": "policy_match"}
tests = [
Action("support-agent", "ticket.read", "tenant/acme/ticket/123"),
Action("support-agent", "ticket.delete", "tenant/acme/ticket/123"),
Action("support-agent", "ticket.read", "tenant/other/ticket/456"),
]
for action in tests:
decision = authorize(action)
print({"action": action.__dict__, "decision": decision})
这个示例体现了一个关键原则:模型可以提出动作,但不能直接决定动作是否获得权限。授权决定应由模型外部的、可测试的控制面完成。
速度越快,越需要自动化控制
智能体安全的风险不只来自单次越权,还来自自动重试、并发调用和长链路累积。系统需要关注几个容易被忽略的指标:
- 单个会话在单位时间内调用工具的次数。
- 访问资源的数量、类型和跨租户情况。
- 被拒绝后是否持续改变参数尝试绕过规则。
- 是否出现读取、聚合、编码和外传等连续动作。
- 是否频繁触发超时、重试或异常错误。
当风险分数超过阈值时,可以采取分级响应:降低速率、停止特定工具、撤销临时凭证、冻结会话,并将完整动作链提交人工调查。重点是先阻断风险,再等待人来分析,而不是让智能体在审核期间继续运行。
落地时的检查清单
上线一个能调用工具的智能体前,至少确认:
- 工具权限是否按智能体、用户、租户和资源范围拆分。
- 高风险参数是否有明确的白名单、上限和格式校验。
- 每次调用是否具备可关联的审计事件。
- 拒绝、暂停、回滚和凭证撤销是否经过演练。
- 攻击测试是否覆盖多轮提示、工具组合和自动重试。
- 策略是否可以版本化、灰度发布和快速回滚。
- 人工审核是否聚焦于不可自动判断的例外,而不是逐条观看所有动作。
17,600 次攻击动作带来的启示很直接:智能体安全不是给模型加一层内容过滤器,而是建立一个能够承受高频试探的控制系统。模型负责提出计划,策略层负责限制权限,观测层负责还原事实,治理层负责持续调整边界。只有这些部分同时工作,智能体的速度才不会变成攻击者的优势。