Agent 工具调用安全设计:最小权限、参数校验与审计闭环

举报
yd_265411139 发表于 2026/10/21 19:21:22 2026/10/21
【摘要】 # Agent 工具调用安全设计:最小权限、参数校验与审计闭环Agent 真正危险的地方,往往不是“回答错了”,而是它把自然语言请求转换成了真实操作:读取文件、查询数据库、发送消息、修改配置,甚至调用其他系统。只要工具具备写权限,一句被污染的网页内容就可能变成一次越权操作。因此,Agent 安全不能只依赖提示词里的一句“不要执行危险操作”。更可靠的做法,是把模型输出视为不可信输入,在工具网关...

# Agent 工具调用安全设计:最小权限、参数校验与审计闭环

Agent 真正危险的地方,往往不是“回答错了”,而是它把自然语言请求转换成了真实操作:读取文件、查询数据库、发送消息、修改配置,甚至调用其他系统。只要工具具备写权限,一句被污染的网页内容就可能变成一次越权操作。

因此,Agent 安全不能只依赖提示词里的一句“不要执行危险操作”。更可靠的做法,是把模型输出视为不可信输入,在工具网关中完成身份、权限、参数和副作用的确定性校验。

## 一、先识别真正的威胁

设计工具权限前,可以先列出四类风险:

1. **提示词注入**:不可信文档或网页要求 Agent 忽略原任务,转而读取其他资源。
2. **权限混淆**:Agent 继承了用户的全部权限,却代表用户操作了另一个租户的数据。
3. **参数越界**:工具名合法,但参数中的资源 ID、路径或收件人被替换。
4. **副作用放大**:重试、循环调用或重复确认导致同一操作执行多次。

这些问题有一个共同点:模型可以提出调用建议,但不应该成为最终的授权决策者。授权必须在模型之外完成。

## 二、把“工具”拆成最小能力

一个常见错误是提供 `file_operation`、`database_execute` 这类大而全的工具。工具越通用,校验越难,审计结果也越模糊。

更合理的拆分方式是围绕业务能力设计窄接口:

- `read_report(report_id)`:只能读取当前租户的指定报告。
- `list_reports(page)`:只返回脱敏后的元数据。
- `create_draft(content)`:只创建草稿,不直接发布。
- `publish_report(report_id, approval_token)`:需要有效审批凭证才能执行。
- `delete_report(report_id, approval_token)`:高风险操作独立暴露,并默认关闭。

读写工具分开后,可以给它们设置不同的超时、配额和审批策略。例如,读工具可以高频调用,写工具必须限流并记录审批人。

## 三、权限必须落到资源级

“用户有报告读取权限”仍然过于宽泛。真正要校验的是:

- 当前用户是谁;
- 当前租户是谁;
- 请求的是哪一个资源;
- 操作类型是什么;
- 请求上下文是否一致。

资源 ID 不能由模型自由生成,也不应该在授权前就被信任。工具网关应先校验参数格式,再从可信数据源查询资源归属,最后执行权限判断。

一个简单的授权判断可以抽象为:

```text
allow = subject.tenant_id == resource.tenant_id
        and action in policy[subject.role]
        and resource.state == "active"
        and request.trace_id is not None
```

这里的关键是“资源级”和“服务端查询”。不要只检查前端传来的 `tenant_id`,也不要把权限范围写进由模型控制的参数。

## 四、参数校验要拒绝歧义

自然语言天然存在歧义,而工具参数必须明确。网关应使用白名单校验:

- 资源 ID 必须匹配固定格式并存在;
- 枚举值只能来自服务端定义;
- 路径必须规范化,禁止 `..` 和跨目录访问;
- URL 必须校验协议、域名和重定向目标;
- 文本长度、分页大小和并发数都要有上限;
- 不在允许字段内的参数直接拒绝,而不是静默忽略。

对高风险参数,建议采用“先解析、再规范化、最后校验”的顺序。例如文件名先做 Unicode 规范化,再检查目录边界,避免只在字符串表面做替换。

## 五、副作用操作需要独立确认

发布、转账、删除、授权这类动作,不应和普通查询共用同一条自动执行链路。可以采用三层控制:

1. **草稿层**:Agent 只能生成变更计划或草稿。
2. **确认层**:展示目标资源、变更内容和影响范围,由用户明确确认。
3. **执行层**:校验一次性审批令牌,执行后立即失效。

审批应绑定具体操作摘要,而不是一个宽泛的“允许所有操作”。例如审批令牌应包含资源 ID、动作、参数哈希、发起人和过期时间。执行层再次校验摘要,避免审批后参数被替换。

## 六、示例:工具网关的最小骨架

下面是一个不依赖具体框架的 Python 示例,重点展示“模型只提交意图,网关负责授权”的结构。

```python
from dataclasses import dataclass
from hashlib import sha256
from typing import Any


class ToolDenied(Exception):
    pass


@dataclass(frozen=True)
class Actor:
    user_id: str
    tenant_id: str
    roles: frozenset[str]


@dataclass(frozen=True)
class Resource:
    resource_id: str
    tenant_id: str
    state: str


READ_ROLES = frozenset({"viewer", "editor", "owner"})
WRITE_ROLES = frozenset({"editor", "owner"})


def authorize(actor: Actor, resource: Resource, action: str) -> None:
    if resource.tenant_id != actor.tenant_id:
        raise ToolDenied("cross-tenant access denied")
    if resource.state != "active":
        raise ToolDenied("resource is not active")

    allowed_roles = READ_ROLES if action == "read" else WRITE_ROLES
    if actor.roles.isdisjoint(allowed_roles):
        raise ToolDenied("insufficient role")


def operation_digest(
    action: str,
    resource_id: str,
    params: dict[str, Any],
) -> str:
    payload = f"{action}|{resource_id}|{sorted(params.items())}"
    return sha256(payload.encode("utf-8")).hexdigest()


def read_report(actor: Actor, resource: Resource, report_id: str) -> str:
    if resource.resource_id != report_id:
        raise ToolDenied("resource mismatch")
    authorize(actor, resource, "read")
    return load_report_from_trusted_store(resource)


def publish_report(
    actor: Actor,
    resource: Resource,
    report_id: str,
    approval_digest: str,
) -> None:
    params = {"visibility": "tenant"}
    expected = operation_digest("publish", report_id, params)

    if approval_digest != expected:
        raise ToolDenied("approval does not match operation")

    authorize(actor, resource, "write")
    consume_approval_once(approval_digest)
    publish_in_transaction(resource, params)
```

示例中,`Actor` 和 `Resource` 应由认证系统与服务端数据库填充,不能直接采用模型参数。审批摘要绑定了动作和资源,执行前还会再次检查资源归属。

## 七、审计日志要能还原一次决策

只知道“工具调用成功”没有太大价值。可用的审计日志至少应记录:

- 会话 ID 和调用链 ID;
- 用户、租户和 Agent 版本;
- 工具名、规范化后的参数摘要;
- 命中的策略及决策结果;
- 审批人、审批时间和令牌是否被消费;
- 外部系统的请求 ID 和最终状态;
- 输出是否经过脱敏。

敏感参数不应以明文入库。可以使用不可逆哈希做幂等键,使用字段级加密保存必要上下文,并为日志设置与业务数据一致的保留周期。

## 八、上线前的检查清单

- [ ] 每个工具只暴露一个清晰能力。
- [ ] 读写权限分离,高风险写操作默认关闭。
- [ ] 资源归属从可信数据源查询,不能信任模型传入的租户信息。
- [ ] 参数采用白名单校验,并设置长度、数量和频率上限。
- [ ] 发布、删除、授权等操作绑定一次性审批令牌。
- [ ] 所有副作用操作支持幂等键和超时后的状态查询。
- [ ] 工具输出进入模型前完成敏感信息过滤。
- [ ] 审计日志能够还原“谁、在什么上下文、基于哪条策略、做了什么”。

## 结语

Agent 的能力上限取决于工具,而安全底线取决于授权边界。提示词可以减少误触发,但不能替代权限系统。把模型输出当作不可信输入,把能力拆细,把授权放在服务端,把副作用纳入审批和审计,才是更稳妥的工程路径。

一个判断设计是否合格的标准很直接:即使模型完全失控,它也只能在已经明确授予的资源范围内执行有限动作,而且每一次变更都能被追溯和撤销。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。