上篇文章聊了Agent Hook系统——给Agent装刹车。有朋友看完问我:Hook能拦截危险操作,但拦截的前提是你得先告诉它什么能做、什么不能做。这个“告诉”的过程,其实就是权限系统。
今天就来深入探讨一下,为什么传统权限模型(如RBAC)在AI Agent场景下会失灵,以及如何构建一个专属的、安全的Agent权限体系。
一个真实的故事
去年有个团队做内部AI助手,让Agent帮忙管理 Kubernetes 集群。一开始挺顺利的,Agent能自动扩缩容、重启Pod、查看日志。
直到有一天,Agent判断某个Namespace“没用”,直接给删了。
那个Namespace里跑着测试环境的数据库。
整个测试环境瘫痪了三个小时。
事后复盘,问题很简单:Agent拿的是集群管理员权限(cluster-admin),什么都能干。它没有“不该做什么”的概念,只有“做什么能解决问题”的逻辑。
权限不是限制,是保护。 这个道理对人是这样,对Agent更是这样。
为什么传统权限模型不够用?
RBAC的问题
RBAC(基于角色的访问控制)是大家最熟悉的权限模型。用户绑定角色,角色绑定权限,简单明了。
但放到Agent场景下,RBAC有几个硬伤:
1. 权限粒度太粗
RBAC通常在资源级别控制,比如“可以读写数据库”。但Agent的操作是语义级别的——“查询用户表没问题,但不能删除数据”。传统RBAC很难表达这种约束。
2. 缺乏上下文感知
一个Agent在调试阶段和生产阶段需要的权限完全不同。RBAC的角色是静态的,不会根据当前任务动态调整。
3. 权限爆炸
Agent能用的工具可能有几十个,每个工具有多个操作,组合起来权限矩阵巨大。用RBAC管理这个矩阵,维护成本高得离谱。
ABAC也不够
ABAC(基于属性的访问控制)比RBAC灵活,能根据上下文动态判断。但它的问题是规则太复杂,写起来像编程,维护起来像噩梦。
我们需要一种专门为Agent设计的权限模型。
Agent权限系统的核心设计
我总结了几个关键原则,都是踩坑之后才想明白的。
原则一:最小权限,默认拒绝
这是安全领域的铁律,对Agent尤其重要。
class AgentPermission:
"""Agent权限定义"""
def __init__(self):
# 默认所有操作都是禁止的
self._allowed = set()
self._denied = set() # 显式拒绝,优先级最高
def allow(self, tool: str, action: str = "*"):
"""允许某个工具的某个操作"""
self._allowed.add((tool, action))
def deny(self, tool: str, action: str = "*"):
"""显式拒绝某个操作(不可被allow覆盖)"""
self._denied.add((tool, action))
def check(self, tool: str, action: str) -> bool:
"""检查是否有权限"""
# 显式拒绝优先
if (tool, "*") in self._denied or (tool, action) in self._denied:
return False
# 检查是否显式允许
if (tool, "*") in self._allowed or (tool, action) in self._allowed:
return True
# 默认拒绝
return False
# 使用示例
perm = AgentPermission()
perm.allow("sql_query", "SELECT")
perm.allow("sql_query", "INSERT")
perm.deny("sql_query", "DELETE") # 永远不能删
perm.deny("sql_query", "DROP") # 永远不能DROP
perm.allow("file_read", "*") # 可以读所有文件
perm.allow("file_write", "/tmp/*") # 只能写/tmp目录
perm.check("sql_query", "SELECT") # True
perm.check("sql_query", "DELETE") # False
perm.check("file_write", "/tmp/a") # True
perm.check("file_write", "/etc/a") # False
注意那个 deny 的设计——显式拒绝的优先级永远高于 allow。这是为了防止“先allow全部再deny部分”这种危险写法。
原则二:工具级 + 操作级 + 参数级三层控制
光控制“能不能用某个工具”是不够的,还得控制“用这个工具能做什么”,甚至“传什么参数”。
第一层:工具级 → 能不能用 file_write?
第二层:操作级 → 能不能 file_write 到 /tmp?
第三层:参数级 → file_write 的内容不能超过 10MB?
第三层很多人会忽略,但它其实很重要。比如你的Agent可以调用发送邮件的API,你不光要控制它能发给谁,可能还要控制邮件内容的长度、附件的大小。
class ParameterConstraint:
"""参数级权限约束"""
def __init__(self):
self._constraints = {}
def add_constraint(self, tool, param, validator):
"""给某个工具的某个参数添加校验器"""
key = (tool, param)
if key not in self._constraints:
self._constraints[key] = []
self._constraints[key].append(validator)
def validate(self, tool, params):
"""校验参数是否满足约束"""
for (t, param), validators in self._constraints.items():
if t == tool and param in params:
for v in validators:
result = v(params[param])
if not result:
return False, f"参数 {param} 不满足约束"
return True, None
# 使用示例
constraints = ParameterConstraint()
# SQL查询不能超过1000行
constraints.add_constraint("sql_query", "limit",
lambda x: x is not None and x <= 1000)
# 文件写入路径必须在白名单内
constraints.add_constraint("file_write", "path",
lambda x: x.startswith("/tmp/") or x.startswith("/workspace/"))
# API调用频率限制(每分钟不超过10次)
call_count = {"count": 0, "reset_at": time.time() + 60}
constraints.add_constraint("api_call", "endpoint",
lambda x: _rate_limit(call_count, 10))
原则三:权限随任务动态升降
这是我觉得最重要的一点。
Agent不应该一直持有所有权限。权限应该跟着任务走:接手任务时申请,任务完成后回收。
用户:“帮我查一下用户表里今天的注册数据”
↓
Agent申请权限:sql_query.SELECT
↓
系统审批:✅ 通过(只读操作,风险低)
↓
Agent执行查询,返回结果
↓
权限回收
用户:“帮我清理一下测试数据”
↓
Agent申请权限:sql_query.DELETE(限定test_*表)
↓
系统审批:⚠️ 需要人工确认
↓
用户确认后,Agent执行删除
↓
权限回收
这个模式有点像Android的运行时权限申请——不是安装时就给你所有权限,而是用到的时候才问你要。
class DynamicPermissionManager:
"""动态权限管理器"""
def __init__(self):
self._active_permissions = {} # agent_id -> set of permissions
self._approval_policy = {} # permission -> auto/manual
def request_permission(self, agent_id, tool, action, context=None):
"""Agent申请权限"""
perm = (tool, action)
policy = self._approval_policy.get(perm, "manual")
if policy == "auto":
self._grant(agent_id, perm)
return True, "auto_approved"
else:
# 需要人工审批
return False, f"pending_approval:{tool}:{action}"
def approve(self, agent_id, tool, action, ttl_seconds=300):
"""人工审批通过,设置有效期"""
perm = (tool, action)
self._grant(agent_id, perm)
# 5分钟后自动过期
threading.Timer(ttl_seconds,
lambda: self._revoke(agent_id, perm)).start()
def _grant(self, agent_id, perm):
if agent_id not in self._active_permissions:
self._active_permissions[agent_id] = set()
self._active_permissions[agent_id].add(perm)
def _revoke(self, agent_id, perm):
if agent_id in self._active_permissions:
self._active_permissions[agent_id].discard(perm)
原则四:沙箱隔离,权限是最后一道防线
权限控制是逻辑层面的保护。但Agent是AI,它的行为有不确定性。你不能100%保证它不会绕过你的权限检查。
所以权限系统应该是最后一道防线,而不是唯一一道。
在权限系统之外,还应该有:
- 沙箱隔离:Agent在容器或虚拟机里运行,即使拿到权限也出不去
- 网络隔离:限制Agent能访问的网络地址,防止数据外泄
- 资源限制:CPU、内存、磁盘配额,防止Agent把系统搞崩
Kubernetes最近推出的Agent Sandbox就是这个思路——用gVisor或Firecracker给Agent一个强隔离的运行环境,权限系统管逻辑,沙箱管物理。
┌─────────────────────────────────┐
│ Agent 进程 │
│ ┌───────────────────────────┐ │
│ │ 权限系统(逻辑层) │ │
│ │ 工具级 → 操作级 → 参数级 │ │
│ └───────────────────────────┘ │
│ ┌───────────────────────────┐ │
│ │ 沙箱(物理层) │ │
│ │ gVisor / Firecracker │ │
│ │ 网络隔离 + 资源限制 │ │
│ └───────────────────────────┘ │
└─────────────────────────────────┘
实际落地的一些经验
说了这么多理论,分享几个实际落地时的经验。
1. 权限配置要声明式,不要写代码
别在代码里硬编码权限判断逻辑。用YAML或JSON声明权限策略,让非技术人员也能看懂、能修改。
# permissions.yaml
agents:
code_assistant:
allow:
- tool: file_read
path: "/workspace/**"
- tool: file_write
path: "/workspace/**"
- tool: shell_exec
commands: ["git", "npm", "python3"]
deny:
- tool: file_write
path: "/etc/**"
- tool: shell_exec
commands: ["rm -rf", "sudo"]
constraints:
- tool: file_write
param: content
max_size: 10MB
2. 权限变更要有审计日志
每次权限的授予、使用、回收都要记录。出了问题能追溯。
3. 给Agent一个“降级模式”
当Agent的某个操作被权限系统拒绝时,不要直接报错。给它一个降级方案。
比如Agent想删除文件但没权限,可以提示它:“你没有删除权限,但你可以把文件移动到回收目录。”让Agent有机会用更安全的方式完成任务。
4. 定期审查权限
过一段时间就检查一下:Agent实际用了哪些权限?哪些权限从来没触发过?没用的权限就该收回。
写在最后
权限系统这东西,做好了没人会注意到,做差了一次就够喝一壶的。
但我觉得更重要的是一个观念的转变:Agent不是普通的应用程序,它是具有一定自主性的“数字员工”。
你不会给一个新入职的员工公司大门钥匙、财务系统管理员权限、以及服务器root密码。同样的道理,也不应该给 Agent 超出它任务需要的权限。
从最小权限开始,根据实际需要逐步开放,用完就收回。
这不仅是安全最佳实践,也是对Agent能力的正确认知——能力越强,越需要约束。
上一篇聊了Agent Hook系统,这篇聊了权限系统。希望这些从实战中总结的原则,能帮助你更好地设计自己的Agent系统。如果你想了解更多关于AI工程化、云原生和安全设计的深度讨论,欢迎到云栈社区交流。