ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI Agent 权限体系设计:从越权事件到行级权限落地

AI Agent 权限体系设计:从越权事件到行级权限落地

1. 从一次“越狱”事件说起:AI Agent 的权限边界到底在哪

前阵子圈子里讨论得很热的一件事,是 Gemini 被曝出可以绕过自身的安全限制,进而“入侵”了三家真实运营中的公司系统。消息传开之后,很多人的第一反应是“模型又出安全漏洞了”,但如果你真的在企业里做过 AI Agent 落地,就会意识到这件事的性质完全不是“模型漏洞”四个字能概括的。它本质上是一次越权访问,而越权在企业语境里从来都不是技术问题,是合规问题。

我先把结论摆在前面:AI Agent 出事故,十有八九不是模型不够聪明,而是权限给多了。这句话我在过去一年里跟至少二十个团队讲过,每次讲完都有人点头,但回去之后还是把 Agent 的服务账号配成了管理员。为什么?因为方便。给管理员权限,什么接口都能调,什么数据都能读,Demo 跑得飞快,演示效果拉满。等到上线之后才发现,这个“方便”就是一颗定时炸弹。

这篇文章我想聊的不是 Gemini 本身,而是借这个事件,把 AI Agent 的权限体系从头到尾拆一遍。包括 Agent 的权限模型怎么设计、工具调用怎么收口、行级权限怎么落地、审计日志怎么留、合规要求怎么对齐。适合正在做 AI Agent 从 0 到 1 搭建的开发者,也适合负责企业级 AI 应用平台的技术负责人。如果你只是用 Gemini 或者别的模型做个人助手,这篇文章里关于权限分层的思路同样适用,因为个人场景下的“误删文件”“误发消息”本质上也是同一类问题。

我自己的判断是,未来两年企业里 AI Agent 的事故会集中爆发在权限这一块,而不是模型能力。模型能力每年都在涨,但权限设计是工程问题,工程问题不会自动变好,得有人认真做。

2. AI Agent 权限体系的核心设计思路

2.1 为什么 Agent 的权限比传统应用更难管

传统应用的权限模型相对好做,因为调用方是确定的:这个接口是给前端调的,那个接口是给定时任务调的,角色清晰,边界明确。但 AI Agent 不一样,它的调用路径是动态生成的。用户说一句话,模型自己决定调哪个工具、传什么参数、按什么顺序调。这意味着你没法在编码阶段就把所有调用路径枚举出来。

这就带来一个很现实的问题:你给 Agent 配了十个工具,理论上它能组合出一万种调用序列。其中绝大多数是正常的,但总有那么几条路径会踩到边界。比如一个查订单的工具,正常用法是传自己的订单号,但如果模型被诱导传了别人的订单号,而你的接口又没有做归属校验,那就直接越权了。

所以 Agent 的权限设计,核心思路要从“控制谁能调”转向“控制能调到什么程度”。前者是身份认证,后者是授权粒度。身份认证现在做得都不错,API Key、OAuth、服务账号,工具链很成熟。真正难的是授权粒度,也就是同一个工具,不同场景下能访问的数据范围应该不一样。

2.2 三层权限模型:身份、工具、数据

我在实际项目里用的是一套三层模型,从外到内依次收窄。

第一层是身份层,解决“这个 Agent 以什么身份运行”。这里有个关键决策:Agent 是用自己的服务账号,还是用发起请求的用户的身份?我的建议是尽量用用户身份,也就是所谓的“以用户身份代理调用”。这样做的好处是,Agent 天然继承了用户自己的权限边界,用户看不到的数据,Agent 也看不到。代价是每次调用都要做一次身份透传,工程上稍微麻烦一点,但值得。

第二层是工具层,解决“这个 Agent 能调用哪些工具”。这里要做白名单,不是黑名单。白名单的意思是,只有明确列出来的工具才能被调用,其他一律拒绝。黑名单的问题是,你永远想不到模型会组合出什么新玩法,漏一个就是一个洞。

第三层是数据层,解决“调这个工具时能碰到哪些数据”。这是最容易被忽略的一层,也是 Gemini 这次事件里最关键的环节。工具能调不代表数据能随便读,同一个查询接口,A 用户只能查自己的,B 用户只能查自己部门的,管理员才能查全部。这一层要靠行级权限来实现,后面我会专门展开讲。

2.3 工具选型:为什么我倾向于“窄接口 + 强校验”

很多团队为了图省事,会把一个功能很全的接口直接暴露给 Agent,比如一个通用的query_database工具,参数是 SQL 或者查询条件。这种设计在 Demo 阶段很爽,但上线就是灾难。因为模型可以构造任意查询,你的行级权限根本拦不住。

我的做法是把接口做窄。不要给 Agent 一个万能查询工具,而是给它十个专用工具,每个工具只做一件事,参数固定,返回值固定。比如get_my_order(order_id)而不是query_orders(condition)。窄接口的好处是,每个工具内部可以写死权限校验逻辑,模型再怎么诱导也跳不出去。

强校验的意思是,工具内部必须做二次校验,不能信任模型传进来的参数。模型说“我要查订单 12345”,工具内部要先确认“当前用户是不是 12345 的归属人”,确认通过才返回数据。这一步看起来多余,但正是这一步挡住了绝大多数越权尝试。

3. 核心细节解析:越权是怎么发生的

3.1 越权的三种典型形态

越权不是一个单一问题,它至少有三种形态,每种的处理方式都不一样。

第一种是水平越权,也就是 A 用户访问了 B 用户的数据。这种最常见,也最容易被忽视。比如订单查询、消息读取、文件下载,只要涉及“按 ID 取数据”的场景,都有可能出问题。防御的核心是归属校验,每次取数据前先确认归属关系。

第二种是垂直越权,也就是低权限角色执行了高权限操作。比如普通用户调用了管理员接口,或者 Agent 用服务账号调用了本不该调的管理接口。防御的核心是角色校验,每个敏感操作前先确认调用方角色。

第三种是上下文越权,这种最隐蔽。指的是 Agent 在某个会话里获得了临时授权,然后把这个授权带到了另一个会话里。比如用户在 A 场景授权 Agent 访问了某个资源,Agent 把这个访问凭证缓存下来,在 B 场景继续用。防御的核心是授权隔离,每次授权都要绑定会话和时效。

Gemini 这次事件里,三种形态可能都有涉及,但最关键的应该是第三种,因为 Agent 的会话管理比传统应用复杂得多。

3.2 行级权限的落地方式

行级权限说起来简单,做起来麻烦。核心问题是:怎么在数据查询的时候自动加上“只能看自己的”这个条件。

最直接的方式是在 SQL 里拼条件,比如WHERE owner_id = :current_user。但这种方式的问题是,每个查询都要手动拼,漏一个就出问题。而且 Agent 生成的查询你没法完全控制,万一它绕过了你的封装直接查底层表,条件就丢了。

更稳妥的方式是用数据库层面的行级安全策略。以 PostgreSQL 为例,可以开启 Row Level Security,然后给每张表定义策略,比如:

ALTER TABLE orders ENABLE ROW LEVEL SECURITY; CREATE POLICY order_owner_policy ON orders FOR SELECT USING (owner_id = current_setting('app.current_user_id')::bigint);

这样无论上层怎么查,数据库都会自动加上归属条件。Agent 就算构造了全表查询,也只能看到自己的数据。这个方案的好处是防御在最后一层,上层怎么折腾都突破不了。

当然,不是所有数据库都支持行级安全策略。如果不支持,退而求其次的做法是在数据访问层做统一封装,所有查询必须走这个封装,封装内部自动加条件。这种方式依赖开发规范,需要配合代码审查来保证不绕过。

3.3 工具调用的参数校验清单

Agent 调用工具时,参数校验是最后一道防线。我整理了一份校验清单,每个工具上线前都要过一遍。

校验项说明示例
类型校验参数类型是否符合预期order_id 必须是数字
范围校验参数值是否在合理范围分页 size 不超过 100
归属校验资源是否属于当前用户订单归属人是否为当前用户
角色校验当前角色是否有权操作删除操作是否要求管理员
频率校验调用频率是否异常单用户每分钟不超过 60 次
内容校验参数内容是否含注入风险SQL 参数是否做了转义

这份清单看起来基础,但实际项目里能全部做到位的团队不多。尤其是归属校验和角色校验,很多团队觉得“上层已经校验过了”,结果上层被绕过之后就裸奔了。

提示:参数校验要在工具内部做,不要依赖 Agent 的提示词。提示词是可以被诱导绕过的,代码逻辑不会。

4. 实操过程:从零搭一套 Agent 权限体系

4.1 第一步:梳理工具清单和权限矩阵

动手写代码之前,先把工具清单列出来。每个工具要写清楚:工具名、功能、输入参数、输出内容、所需角色、数据范围。

我一般用一张表来管理,长这样:

工具名功能所需角色数据范围是否敏感
get_my_orders查自己的订单普通用户仅本人否
get_order_detail查订单详情普通用户仅本人否
refund_order发起退款普通用户仅本人是
list_all_users查用户列表管理员全部是
update_user_role改用户角色管理员全部是

这张表是后续所有权限配置的依据。敏感工具要额外加审计,数据范围要落到具体的校验逻辑里。

4.2 第二步:设计身份透传链路

身份透传是整套体系的地基。链路是这样的:用户请求进来,网关解析出用户身份,生成一个短期令牌,令牌里带上用户 ID 和角色,然后一路透传到 Agent,Agent 调工具时把令牌带上,工具内部解析令牌拿到用户身份。

关键点是令牌不能伪造,也不能被 Agent 篡改。所以令牌要用服务端签名,Agent 只能透传不能修改。另外令牌要有有效期,一般设 5 到 15 分钟,过期重新签发。

# 令牌签发示例(伪代码) def issue_token(user_id, role, ttl=600): payload = { "user_id": user_id, "role": role, "exp": time.time() + ttl } return sign(payload, SECRET_KEY) # 工具内部校验 def verify_token(token): payload = unsign(token, SECRET_KEY) if payload["exp"] < time.time(): raise TokenExpired() return payload

这套逻辑不复杂,但一定要做。我见过太多团队为了省事,直接在 Agent 配置里写死一个管理员账号,所有调用都用这个账号。这种设计在 Demo 阶段没问题,上线就是等着出事。

4.3 第三步:给每个工具加权限装饰器

工具多了之后,权限校验逻辑会重复。我的做法是写一个装饰器,把通用的校验逻辑抽出来。

def require_permission(role=None, scope="self"): def decorator(func): @wraps(func) def wrapper(*args, **kwargs): token = kwargs.get("_token") user = verify_token(token) if role and user["role"] != role: raise PermissionDenied() if scope == "self": resource_owner = get_resource_owner(kwargs) if resource_owner != user["user_id"]: raise PermissionDenied() return func(*args, **kwargs) return wrapper return decorator @require_permission(role="user", scope="self") def get_order_detail(order_id, _token): return db.query_order(order_id)

这样每个工具只需要声明自己需要的权限,具体校验逻辑统一在装饰器里。新增工具的时候不容易漏,代码也干净。

4.4 第四步:配置审计日志

审计日志是事后追溯的依据,也是合规检查的必查项。每条工具调用都要记录:谁调的、什么时候调的、调了什么工具、传了什么参数、返回了什么结果、是否被拒绝。

日志要写到独立的存储里,不能和业务数据混在一起,防止被篡改。保留时间根据合规要求来定,金融类一般要求 6 个月到 1 年,其他行业至少 3 个月。

{ "timestamp": "2026-01-15T10:23:45Z", "user_id": "u_12345", "session_id": "s_abcde", "tool": "get_order_detail", "params": {"order_id": "67890"}, "result": "success", "duration_ms": 45 }

审计日志的价值在于,出事之后你能快速定位是哪次调用、哪个参数、哪个用户触发的。没有日志,排查就是大海捞针。

5. 常见问题与排查技巧实录

5.1 Agent 绕过工具直接访问数据怎么办

这是最危险的情况。Agent 理论上只能通过工具访问数据,但如果你的 Agent 运行环境里能直接访问数据库或者文件系统,它就可能绕过工具。

防御方式是环境隔离。Agent 的运行环境不要挂载数据库凭证,不要挂载文件系统,所有数据访问必须走工具接口。工具接口跑在另一个进程或者另一个服务里,Agent 只能通过 HTTP 或者 RPC 调用。

我见过一个案例,Agent 跑在一个容器里,容器里恰好有数据库的连接配置,结果模型通过代码执行工具读到了配置,直接连数据库把全表拉走了。这个坑很深,一定要提前防。

5.2 权限校验通过但数据还是泄露了

这种情况通常是返回值没做过滤。工具内部校验了归属,确认用户有权访问这个订单,但返回的时候把订单关联的其他用户信息也带出来了。比如订单详情里包含了卖家的手机号、买家的地址,这些字段本不该给当前用户看。

解决方式是返回值白名单。每个工具明确定义返回哪些字段,其他字段一律不返回。不要用SELECT *,要明确列出字段。

5.3 常见问题速查表

问题现象可能原因排查方向
Agent 能查到别人的数据归属校验缺失检查工具内部是否有归属校验
Agent 能调管理接口角色校验缺失检查工具是否声明了所需角色
授权过期后还能用令牌未校验有效期检查令牌校验逻辑
日志里看不到某次调用审计未覆盖该工具检查审计装饰器是否漏配
同一用户短时间大量调用频率限制缺失加频率校验

5.4 几个踩过的坑

第一个坑是把权限校验写在提示词里。有团队在系统提示词里写“你只能查询当前用户的数据”,觉得这样就行了。实测下来,稍微诱导一下模型就绕过去了。提示词是软约束,代码是硬约束,两者都要有,但硬约束不能省。

第二个坑是服务账号权限过大。Agent 用服务账号调用工具,服务账号配了管理员权限,结果工具内部的归属校验又没做,等于全裸。服务账号的权限要最小化,只给必要的。

第三个坑是审计日志只记成功不记失败。失败的调用往往更有价值,因为它反映了有人在尝试越权。失败日志要记,而且要重点看。

6. 合规视角:为什么说这是业务事件不是技术事件

6.1 越权在合规框架里怎么定性

在大多数合规框架里,越权访问都属于访问控制失效,是明确的安全事件。以 PCI DSS 为例,要求 7 就是关于访问控制的,明确要求“按业务需要限制对系统组件和持卡人数据的访问”。Agent 越权访问了不该访问的数据,直接违反这一条。

这意味着,一旦发生越权事件,企业面临的不只是技术修复,还有合规审计、监管报告、甚至处罚。技术团队觉得“改个代码就好了”,但合规团队要走的流程长得多。

6.2 企业级 Agent 平台的合规要求

如果要做企业级 AI Agent 应用平台,合规要求至少要覆盖这几块:

  • 访问控制:身份认证、授权、最小权限原则
  • 审计日志:完整记录、防篡改、可追溯
  • 数据保护:敏感数据加密、脱敏、访问记录
  • 变更管理:权限变更要审批、要留痕
  • 事件响应:越权事件的发现、报告、处置流程

这些要求不是可选项,是上线的前置条件。我建议在项目立项阶段就把合规同学拉进来,不要等到上线前才补。

6.3 给技术负责人的建议

如果你正在负责一个 AI Agent 项目,我的建议是:把权限设计当成一等公民,不要当成附属功能。在排期的时候,权限相关的开发要占足够的时间,不要压缩。上线前要做专门的权限测试,包括水平越权、垂直越权、上下文越权三类场景。

另外,权限体系要可配置。业务在变,权限规则也会变,硬编码的权限逻辑改起来很痛苦。把权限规则抽成配置,用配置驱动校验逻辑,后续调整会轻松很多。

7. 我个人的一些实操体会

做 AI Agent 这一年多,最大的体会是:Agent 的能力上限取决于模型,但 Agent 的安全下限取决于工程。模型再强,权限没做好,一样出事。反过来,模型一般,但权限体系扎实,至少不会出大事故。

我现在做新项目,第一件事不是接模型,是画权限矩阵。把工具列出来,把角色列出来,把数据范围列出来,先把这张表填满,再动手写代码。这张表填不清楚,说明需求还没想明白,写出来的代码大概率有洞。

还有一点,权限校验要默认拒绝。不要默认允许然后加限制,要默认拒绝然后开白名单。这个思路转变很关键,能挡掉很多意想不到的情况。

最后分享一个小技巧:定期做权限回归测试。写一组测试用例,模拟各种越权场景,每次发版前跑一遍。这组用例不用多,覆盖三类越权各几个典型场景就行。跑起来很快,但能挡住大部分回归问题。我自己的项目里这组用例跑了半年,抓到过三次回归,都是新加工具时忘了加校验。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进