Sooua
登录
返回文章列表
AI Agent 工程化··11 分钟阅读·1 次阅读

Agent Tool Calling 的安全控制面:权限边界、审批链路与审计回放

从权限边界、审批链路和审计回放三个层面,构建面向生产环境的 Agent Tool Calling 安全控制面。

Agent Tool Calling 的安全控制面:权限边界、审批链路与审计回放

0. 核心观点

AI Agent 的安全问题,不应被简单理解为“提示词注入防护”。真正的风险来自三个条件同时成立:

  1. 模型会被自然语言影响决策
  2. Agent 拥有工具调用能力
  3. 工具背后连接了真实系统、真实数据和真实权限

传统 LLM 应用出错,通常表现为“回答错了”。Agent 出错,则可能表现为“删了文件、发了邮件、改了数据库、调用了生产接口”。因此,Agent 安全的工程核心不是让模型“更听话”,而是把 Agent 当作一个不完全可信的执行主体来治理。

一句话总结:

Prompt 不是安全边界,工具网关、权限模型、审计系统和人工确认才是安全边界。

Agent 工具调用安全架构


1. 为什么 Tool Calling 是 Agent 风险放大的关键

Agent 与普通聊天机器人的最大区别不是“会规划”,而是“能行动”。工具调用把模型输出从文本空间带入执行空间:

  • 搜索工具让 Agent 接触不可信外部内容;
  • 文件工具让 Agent 读写本地资产;
  • 邮件、IM、Webhook 工具让 Agent 具备数据外发能力;
  • Shell / Code Interpreter 让 Agent 具备主机级影响面;
  • 数据库、CRM、工单、云 API 让 Agent 进入企业业务系统。

这意味着攻击者不一定需要攻破服务器,只要能影响 Agent 的决策路径,就可能借 Agent 的合法工具完成非法动作。

1.1 风险链路

如果缺少 工具安全网关,Agent 的安全性就被退化成“模型是否能稳定拒绝攻击指令”。这是不可靠的。


2. 威胁模型:攻击者真正想拿到什么

Agent 攻击通常不是为了让模型说一句违规内容,而是为了达成可执行目标。

攻击目标典型方式真实危害
目标劫持诱导 Agent 改变任务目标从“总结文档”变成“外发敏感信息”
越权工具调用诱导调用不该调用的工具删除、转账、改配置、发邮件
数据外泄借合法工具传输敏感数据API Key、客户数据、内部文档泄露
记忆投毒把恶意内容写入长期记忆/RAG后续会话持续被污染
资源消耗诱导循环调用、递归规划Token 成本、API 费用、任务队列阻塞
供应链污染恶意工具、插件、MCP ServerAgent 动态加载恶意能力

关键判断标准:

只要一个工具可以改变外部世界,它就必须被纳入安全控制面,而不能只靠提示词约束。

Tool Calling 风险矩阵


3. 工程原则:把 Agent 当作“不可信用户态进程”

一个有用的类比是:Agent Runtime 类似操作系统内核,LLM 类似不可信用户态进程,工具类似系统调用,文件/数据库/API 类似受保护资源。

这个模型带来 5 条设计原则:

  1. 默认不信任模型输出:模型输出只是“建议调用”,不是“执行授权”。
  2. 工具调用必须经过策略裁决:工具名、参数、上下文、用户身份、任务意图都要参与判断。
  3. 权限按任务发放,而不是按 Agent 发放:同一个 Agent 在不同任务中应拿到不同权限。
  4. 高风险动作必须人机确认:删除、转账、外发、改权限、执行命令不能全自动。
  5. 所有动作可回放、可追责、可熔断:没有审计的 Agent 不能进入生产。

4. 安全架构:Tool Gateway 是核心控制点

推荐把所有工具调用收敛到统一的 Tool Gateway,而不是让 Agent 直接调用底层函数。

4.1 调用流程

4.2 Tool Gateway 应检查什么

每次工具调用至少需要检查:

  • 工具是否在白名单中
  • 调用者身份是否可信
  • 当前任务是否需要该工具
  • 参数是否符合 Schema
  • 参数是否包含敏感字段或危险操作
  • 目标资源是否在授权范围内
  • 调用频率是否异常
  • 是否需要人工确认
  • 是否需要脱敏记录日志

5. 最小权限设计:不要给 Agent 万能钥匙

错误设计:

# 反例:Agent 拿到通用命令执行能力
TOOLS = [
    {
        "name": "execute_command",
        "description": "Execute any shell command",
        "allowed_commands": "*",
    }
]

这个设计的问题不是“提示词写得不够安全”,而是能力边界本身不存在。正确做法是把工具拆小、权限收窄、参数结构化。

TOOLS = [
    {
        "name": "read_project_report",
        "description": "Read markdown reports under the approved project directory.",
        "allowed_operations": ["read"],
        "allowed_paths": ["/workspace/reports/*.md"],
        "blocked_patterns": ["*.env", "*.pem", "*secret*", "id_rsa"],
    },
    {
        "name": "create_draft_note",
        "description": "Create a draft markdown note; never overwrite existing files.",
        "allowed_operations": ["create"],
        "allowed_paths": ["/workspace/drafts/*.md"],
        "overwrite": False,
    },
]

5.1 权限颗粒度建议

能力类型推荐权限模型
文件读取目录级 allowlist + 文件类型限制 + secret pattern blocklist
文件写入只能写 draft 区域,默认不覆盖
数据库只读代理账号 + 字段级授权 + 行数限制
邮件/IM默认只能生成草稿,发送需人工确认
Shell默认禁用;必须使用命令 allowlist 和沙箱
云 API临时 Token + 资源标签约束 + 操作级策略
MCP Server签名校验 + 来源白名单 + 工具描述安全审查

6. 参数校验:工具调用不能只校验语法,还要校验语义

很多系统只检查 JSON 是否符合 Schema,但攻击经常藏在“语义合法但业务危险”的参数里。

from dataclasses import dataclass
from enum import Enum
import re
 
class Decision(str, Enum):
    ALLOW = "allow"
    DENY = "deny"
    REQUIRE_APPROVAL = "require_approval"
 
@dataclass
class ToolDecision:
    decision: Decision
    reason: str
 
class ToolPolicyEngine:
    def __init__(self):
        self.allowed_tools = {
            "read_ticket": {
                "risk": "low",
                "required_role": "support_read",
                "ticket_id_pattern": r"^TICKET-[0-9]{6}$",
            },
            "send_external_email": {
                "risk": "high",
                "required_role": "mail_draft",
            },
            "delete_record": {
                "risk": "critical",
                "required_role": "admin_delete",
            },
        }
 
    def evaluate(self, user_roles: set[str], tool_name: str, args: dict) -> ToolDecision:
        if tool_name not in self.allowed_tools:
            return ToolDecision(Decision.DENY, "tool_not_allowlisted")
 
        policy = self.allowed_tools[tool_name]
        if policy["required_role"] not in user_roles:
            return ToolDecision(Decision.DENY, "missing_required_role")
 
        # 示例:业务参数校验
        if tool_name == "read_ticket":
            ticket_id = args.get("ticket_id", "")
            if not re.match(policy["ticket_id_pattern"], ticket_id):
                return ToolDecision(Decision.DENY, "invalid_ticket_id")
 
        # 示例:敏感动作强制审批
        if policy["risk"] in {"high", "critical"}:
            return ToolDecision(Decision.REQUIRE_APPROVAL, f"risk={policy['risk']}")
 
        return ToolDecision(Decision.ALLOW, "policy_passed")

这里的关键不是代码本身,而是设计思想:

  • LLM 负责提出动作;
  • Policy Engine 负责判断动作是否允许;
  • Tool Gateway 负责执行和记录;
  • Human 负责确认高风险后果。

7. 记忆安全:长期记忆不是垃圾桶

Agent 记忆系统是另一个高危区域。攻击者可以通过多轮对话、文档上传、RAG 数据写入等方式污染记忆,让 Agent 在未来任务中持续受影响。

7.1 记忆写入前必须做四件事

  1. 来源标记:这条记忆来自用户、系统、工具、网页还是人工审核?
  2. 信任分级:是否允许影响未来决策?还是只能作为普通参考?
  3. 敏感信息扫描:是否包含凭据、Token、个人信息、内部路径?
  4. 过期策略:这条记忆何时失效?是否允许跨会话复用?
@dataclass
class MemoryItem:
    content: str
    source: str              # user / tool / web / admin
    trust_level: str         # untrusted / verified / system
    scope: str               # session / user / org
    expires_at: str | None
    checksum: str

7.2 记忆隔离原则

场景隔离策略
多用户 Agent用户级 memory namespace
多租户 SaaS Agent租户级物理或逻辑隔离
RAG 知识库数据源签名 + ingestion 审批
工具返回结果默认不写长期记忆
外部网页内容永不作为高信任记忆

8. 人工确认:不要让 Agent 自己按下红色按钮

高风险动作必须给人类一个清晰、不可被模型美化的动作预览。

错误确认文案:

Agent 认为这是安全操作,是否继续?

正确确认文案:

即将向 [email protected] 发送 12 条客户记录摘要。该操作会离开企业边界,发送后无法撤回。是否确认?

8.1 高风险动作清单

  • 删除文件、数据库记录、云资源;
  • 修改权限、密钥、网络策略;
  • 对外发送邮件、Webhook、IM;
  • 执行 Shell、Python、SQL 写操作;
  • 金融支付、退款、审批;
  • 批量读取敏感数据;
  • 安装插件、加载 MCP Server、更新依赖。

9. 审计与可观测性:Agent 必须能被 Replay

生产级 Agent 至少记录以下审计事件:

{
  "event_type": "tool_call_decision",
  "trace_id": "tr_20260606_001",
  "user_id": "u_123",
  "agent_id": "agent_ops",
  "task_id": "task_456",
  "tool_name": "send_external_email",
  "risk_level": "high",
  "decision": "require_approval",
  "reason": "external_communication",
  "args_digest": "sha256:...",
  "timestamp": "2026-06-06T02:00:00+08:00"
}

注意:日志里不应明文保存完整敏感参数。推荐记录:

  • 参数摘要;
  • 脱敏后的动作预览;
  • 策略命中原因;
  • 审批人和审批时间;
  • 工具返回状态;
  • trace_id/span_id,便于回放。

10. 生产落地检查清单

10.1 架构层

  • 所有工具调用经过统一 Tool Gateway;
  • Agent 不能直接访问生产数据库、Shell、云 API;
  • 工具按能力拆分,而不是提供万能工具;
  • 高风险动作支持人工确认和回滚;
  • 多 Agent 通信有身份认证、签名和防重放机制。

10.2 权限层

  • 使用任务级临时权限;
  • 文件、数据库、API 都有资源级 allowlist;
  • 默认禁用外发能力;
  • 默认禁用任意代码执行;
  • MCP/插件来源经过签名或白名单控制。

10.3 数据层

  • 外部输入标记为 untrusted;
  • 敏感数据进入上下文前脱敏;
  • 长期记忆有来源、信任级别和过期时间;
  • RAG 数据有 ingestion 审批和版本记录;
  • 日志脱敏且不可被 Agent 自己篡改。

10.4 运行层

  • 工具调用限速、限深度、限成本;
  • 异常行为触发熔断;
  • 支持任务级 kill switch;
  • 支持 trace replay;
  • 定期进行 Agent Red Team 测试。

11. 延伸思考:Least Privilege 还不够,要 Least Agency

传统最小权限关注的是“能不能访问资源”。Agent 场景还要进一步关注“是否需要自主执行”。

所以更准确的原则是 Least Agency,最小自主性

  • 能生成建议,就不要自动执行;
  • 能生成草稿,就不要直接发送;
  • 能读摘要,就不要读全文;
  • 能读只读视图,就不要连生产库;
  • 能单步确认,就不要多步连锁自动化。

Least Agency 最小自主性检查清单

Agent 的价值来自自动化,但安全的 Agent 平台必须允许我们精确控制自动化边界。

分享

评论

登录 后参与讨论。

加载中…

相关文章