Skip to content

权限系统 🔥

Agent 的威力来自能调用工具,而工具中最危险的是 BashWrite--一条 rm -rf / 或一次对 ~/.bashrc 的覆写就足以毁掉整个环境。AgentScope 2.x 把"工具执行前的拦截"做成了一等公民: Permission 子系统在每一次工具调用真正执行前做一次三态决策allow / deny / ask), 配合五种模式、可配置规则与 Bash 静态分析,构成 Agent 执行链上的安全闸门。

版本基线:本文以 Python agentscope 2.0.4(PyPI,2026-07-07)为准,要求 Python >=3.11。 权限相关类型从 agentscope.permission 导入,公开符号为 PermissionContextAdditionalWorkingDirectoryPermissionDecisionPermissionEnginePermissionRulePermissionModePermissionBehavior(源:src/agentscope/permission/__init__.py)。2.x 文档站 存在版本化陷阱:非版本化的 /en/ URL 会 404,本页所有官方链接均使用 versions/2.0.4/en/ 形式。

为什么需要权限系统

Agent 的 ReAct 循环会自主决定调用哪个工具、传什么参数。一旦模型生成了一条 Bash 工具调用, 默认行为就是直接执行--这在本地开发时或许可以接受,但在生产、多租户或无人值守场景下是灾难性的。 2.x 在工具执行前插入了一层 PermissionEngine 检查:它综合规则(PermissionRule)、模式 (PermissionMode)与工具自身的运行时分析(check_permissions / check_read_only),给出 allow / deny / ask 三种决策之一。ask 会暂停 Agent 并产出 HITL 事件,等待用户裁决。

前端类比

如果你来自前端,可以把权限系统类比为 Express 的 auth middleware + 三态闸门

  • auth middleware 决定请求"放行 / 拒绝 / 重定向到登录页",对应 allow / deny / ask
  • 模式 类似 middleware 的预设策略:开发模式放行所有请求(BYPASS)、只读模式拒绝所有写操作 (EXPLORE)、生产模式默认拒绝未鉴权请求(DONT_ASK)。
  • 规则 类似路由级 allowlist / denylist:app.post('/api/*', allowIfRole('admin'))

AgentScope 原生语义:权限系统不是 HTTP middleware,而是一个在工具调用同步路径上的决策引擎。 它检查的不是用户身份,而是"这个 Agent 此刻想用这个工具做这件事,是否被允许"。ask 不是重定向, 而是暂停整个 ReAct 循环,通过 RequireUserConfirmEvent 把决策权交还给人,恢复后再继续。这与 Express 的请求 / 响应模型完全不同:Agent 的执行是长生命周期的异步流,一次 ask 可能让 Agent 暂停数分钟乃至数小时。

三态决策流程

下面这张流程图说明一次工具调用如何穿过 PermissionEngine 得到三态决策。图前先说明:决策的输入 是工具名 + 实际调用参数 + 当前 PermissionContext;输出是 PermissionDecision,其 behavior 字段为 ALLOW / DENY / ASK 之一(PASSTHROUGH 是工具表示"无意见,交给引擎"的内部值, 不直接作为最终决策)。

图后解释关键路径:

  • 规则优先PermissionRule 的匹配优先级最高。命中 DENY 或显式 ASK 规则时直接拍板, 不再进入工具内置检查。DENYASK 规则在所有模式下都生效,包括 BYPASS
  • 工具内置检查:每个工具实现 check_permissions(),针对实际调用参数做运行时分析。例如 Bash 会解析命令字符串判断是否只读,Write 会检查目标路径是否属于危险路径(.bashrc / .ssh/ 等)。
  • PASSTHROUGH 是工具表示"我没有特殊意见"的内部值,引擎收到后会按当前 PermissionMode 兜底。
  • bypass_immune ASK:工具认为过于危险、不应被静默放行的 ASK(如 rm -rf /、写 ~/.bashrc)。 在 DEFAULT / ACCEPT_EDITS / DONT_ASK 下,即使有匹配的 ALLOW 规则也无法压制它;在 DONT_ASK 下被转为 DENY;在 BYPASS 下被有意跳过
  • ask 暂停与恢复ask 决策会让 Agent 产出 RequireUserConfirmEvent 并暂停。用户批准时 可以顺带接受 suggested_rule,该规则会被持久化进引擎,后续同类调用自动放行不再询问。

五种模式

PermissionMode 是一个全局策略,决定"没有规则匹配时怎么办"。它可以在构造 Agent 时设定,也可以 运行时切换(见 运行时切换模式)。

模式行为何时用
DEFAULT所有操作都需要显式规则或用户确认。唯一自动放行的是 Bash 识别为只读的命令(ls / git status / cat 等)最安全,推荐默认
ACCEPT_EDITS自动放行工作目录内的文件操作;自动放行 Bash 文件系统命令(mkdir/touch/rm/cp/mv/sed),但要求每个目标路径都解析进工作目录开发时有人在旁边盯着
EXPLORE只读:放行只读工具与只读 Bash 命令,拒绝所有修改。用户配置的 DENY / ASK 规则优先于只读自动放行代码探索、规划阶段
BYPASS跳过所有权限检查,包括工具安全 ASKrm -rf /、写 ~/.bashrc 等都会放行)。只有 DENY / ASK 规则和工具 DENY 仍然生效沙箱环境或完全可信的运行
DONT_ASK把所有 ASK(默认 ASK、ASK 规则、安全 ASK)都转为 DENY;无交互场景的安全默认无人值守 / 定时执行

⚠️ BYPASS 不是"更宽松的 DEFAULT":它有意跳过工具内置的安全 ASK。官方文档明确指出 BYPASSrm -rf /、写 ~/.bashrc、命令注入等都会直接放行。只有在运行环境本身是沙箱 (DockerWorkspace / E2BWorkspace)或完全可信时才应使用,且必须配合 deny_rules 保护 关键路径。无人值守场景应优先用 DONT_ASK,它保留了安全网且永不弹窗。

规则系统

PermissionRule

PermissionRule 把"某个工具 + 某种调用模式"映射到一种行为。四个字段如下(源:官方 2.0.4 文档 与 src/agentscope/permission/_rule.py):

字段类型作用
tool_namestr规则适用的工具名:"Bash""Read""Write""Edit" 或任意自定义工具名
rule_contentstr | None匹配模式,语义随工具变化(见下表)
behaviorPermissionBehaviorALLOW / DENY / ASKPASSTHROUGH 是工具内部值,不用于规则)
sourcestr规则来源:"userSettings""projectSettings""session"

rule_content 的匹配语义由每个工具的 match_rule() 方法决定,因此不同工具的模式语法不同:

工具匹配对象模式语法示例
Bashcommand 参数通配前缀 PREFIX:*npm run:* 匹配 npm run build,不匹配 npm install
Read/Write/Editfile_path 参数glob(fnmatchsrc/**/*.py 匹配 src/ 下任意 .py
其他工具JSON 序列化参数精确匹配取决于工具实现

PermissionBehavior

PermissionBehavior 是一个枚举,有四个值。用户面向的是前三个,PASSTHROUGH 是工具内部值:

含义
ALLOW放行,工具立即执行
DENY拒绝,生成错误 ToolResult 回灌进上下文,模型可以看到
ASK暂停 Agent,产出 RequireUserConfirmEvent 等待用户裁决
PASSTHROUGH工具表示"无意见",交给引擎按模式兜底(不用于规则配置)

AdditionalWorkingDirectory

AdditionalWorkingDirectory 声明一个 Agent 可以读写的额外目录,用在 ACCEPT_EDITS 模式下判断 文件操作是否落在允许范围内。两个字段:

字段类型作用
pathstr目录绝对路径
sourcestr来源:"userSettings" / "session"

PermissionContext

PermissionContext 是规则的容器,通过 AgentState.permission_context 注入 Agent。它持有:

  • mode: PermissionMode - 当前模式。
  • working_directories: dict[str, AdditionalWorkingDirectory] - 工作目录集合,键为路径。
  • allow_rules: dict[str, list[PermissionRule]] - 按工具名分组的 allow 规则。
  • deny_rules: dict[str, list[PermissionRule]] - 按工具名分组的 deny 规则。

DENY 与显式 ASK 规则在所有模式下都生效(包括 BYPASS),是真正的硬护栏。

构造带权限的 Agent

下面是一个完整构造示例:ACCEPT_EDITS 模式 + 工作目录 + allow / deny 规则。凭据通过环境变量 注入,不要写死在代码里。

python
import os

from agentscope.agent import Agent
from agentscope.state import AgentState
from agentscope.model import DashScopeChatModel
from agentscope.credential import DashScopeCredential
from agentscope.permission import (
    PermissionContext,
    PermissionMode,
    PermissionBehavior,
    PermissionRule,
    AdditionalWorkingDirectory,
)

agent = Agent(
    name="dev_agent",
    system_prompt="你是一个能读写代码的助手,只在当前项目目录内操作。",
    model=DashScopeChatModel(
        credential=DashScopeCredential(
            api_key=os.environ["DASHSCOPE_API_KEY"],
        ),
        model="qwen-plus",
    ),
    state=AgentState(
        permission_context=PermissionContext(
            mode=PermissionMode.ACCEPT_EDITS,
            working_directories={
                "/my/project": AdditionalWorkingDirectory(
                    path="/my/project",
                    source="userSettings",
                ),
            },
            allow_rules={
                "Bash": [
                    PermissionRule(
                        tool_name="Bash",
                        rule_content="npm run:*",
                        behavior=PermissionBehavior.ALLOW,
                        source="userSettings",
                    ),
                ],
                "Write": [
                    PermissionRule(
                        tool_name="Write",
                        rule_content="src/**",
                        behavior=PermissionBehavior.ALLOW,
                        source="userSettings",
                    ),
                ],
            },
            deny_rules={
                "Bash": [
                    PermissionRule(
                        tool_name="Bash",
                        rule_content="rm:*",
                        behavior=PermissionBehavior.DENY,
                        source="userSettings",
                    ),
                ],
            },
        ),
    ),
)

上面的配置含义:Agent 可以在 /my/project 内自由写文件;npm run * 命令自动放行;任何 rm 命令无论是否在工作目录内都被拒绝。ACCEPT_EDITS 模式下,工作目录内的 mkdir / touch / cp 等 Bash 文件系统命令也会自动放行,前提是每个目标路径都解析进 /my/project

运行时切换模式

模式不是一成不变的。PermissionContext 挂在 AgentState 上,可以直接修改 mode 字段:

python
from agentscope.permission import PermissionMode

# 探索阶段:切到只读,Agent 只能看不能改
agent.state.permission_context.mode = PermissionMode.EXPLORE

# 用户确认方案后:切回可写模式开始改代码
agent.state.permission_context.mode = PermissionMode.ACCEPT_EDITS

# 批量执行时:切到无人值守模式,ASK 全部转 DENY
agent.state.permission_context.mode = PermissionMode.DONT_ASK

运行时切换不会丢失已积累的规则--包括 HITL 中用户接受的 suggested_rule。这些规则会随 PermissionContext 一起持久化进 AgentState(见 状态与会话)。

Bash 静态分析与 bypass_immune

管道命令解析

Bash 工具内置静态分析(源:src/agentscope/permission/_bash_parser.py),在调用时解析命令字符串, 判断整体是否只读。规则是:

  • 复合命令&& / || / ; / |)只有当所有子命令都是只读时,整体才判为只读。 ls && rm x 不是只读,因为有 rm
  • 输出重定向> / >>)会让命令变为非只读,即使原本是只读命令。ls > files.txt 不是只读。
  • 只读命令清单(部分):lscatheadtailgreprgfindtreestatwcpwdwhichgit status / git log / git diff / git show / git branch / git blamedocker ps / docker images / docker logs / docker inspectgh repo view / gh issue listnpm list / pip list / pip show

只读命令在所有模式下(包括 DEFAULT)都自动放行,无需配置规则。这是 DEFAULT 模式下唯一的 自动放行路径。

bypass_immune 安全规则

工具在 check_permissions() 中可以返回带 bypass_immune=TruePermissionDecision,表示这个 ASK 过于危险、不应被静默压制。PermissionDecision 的关键字段:

字段类型作用
behaviorPermissionBehavior决策行为
messagestr给人看的说明
decision_reasonstr决策原因(内部记录)
bypass_immuneboolTrue 时,此 ASK 在多数模式下无法被 ALLOW 规则压制

bypass_immune=True 的 ASK 在各模式下的处理(源:官方 2.0.4 文档):

模式bypass_immune=True ASK 的处理
DEFAULT生效,ALLOW 规则无法压制
ACCEPT_EDITS生效,同 DEFAULT
EXPLORE不适用(引擎在 EXPLORE 下不调用 check_permissions,只读判定即终判)
BYPASS有意跳过--BYPASS 的契约是"用户已放弃安全提示,只剩规则护栏"
DONT_ASK转为 DENY(没有用户可以回答)

典型场景:Write 工具检测到目标路径是 ~/.bashrc,返回 bypass_immune=True 的 ASK。在 DEFAULT 下即使用户配了 allow_rules["Write"] = ["**"] 也无法静默放行;在 BYPASS 下则被跳过, 因此 BYPASS 必须配合 deny_rules 保护这些路径。

与 HITL 的关系

ask 决策是权限系统与 HITL(Human-in-the-Loop)的交汇点。当 PermissionEngine 返回 ASK 时, Agent 会:

  1. 产出 RequireUserConfirmEvent,其中携带工具调用信息与自动生成的 suggested_rules
  2. 暂停 ReAct 循环,reply 返回当前已累积的 Msgreply_stream 产出该 Require 事件。
  3. 等待用户通过 UserConfirmResultEvent 恢复。用户可以:
    • 批准并接受 suggested_rule,该规则被持久化进引擎,后续同类调用自动放行。
    • 批准但不接受规则(只本次放行)。
    • 拒绝,工具生成错误 ToolResult 回灌进上下文,模型据此调整策略。

完整的 HITL 代码模式(接收事件 -> 构造结果 -> 恢复)在 流式事件 页展开。本页只需记住:权限系统的 ask 是 HITL 的主要触发源之一,另一个来源是 is_external_tool=True 的外部工具(触发 RequireExternalExecutionEvent)。

常见错误

  • BYPASS 模式滥用:把 BYPASS 当成"省去确认"的便捷开关。BYPASS 会跳过工具内置安全 ASK, rm -rf /、写 ~/.bashrc、命令注入都会放行。只有在沙箱(DockerWorkspace / E2BWorkspace) 或完全可信环境才用,且必须配 deny_rules。无人值守场景用 DONT_ASK 更安全。

    ⚠️ DONT_ASK 优于 BYPASS 用于无人值守DONT_ASK 保留工具安全网,只是把 ASK 转 DENY; BYPASS 则完全放弃安全网。两者的"不弹窗"效果相同,但 DONT_ASK 的事故半径小得多。

  • rule_content 写错语法BashPREFIX:* 前缀通配,不是 glob;Write / Edit / Readfnmatch glob。把 npm run:* 写成 npm run*npm run/* 都不会匹配。正确写法是 命令前缀:*,例如 npm run:* 匹配 npm run build

  • 误以为权限替代沙箱:权限是闸门,决定工具"能不能执行";沙箱是执行隔离,决定工具"在哪里 执行"。BYPASS 模式 + LocalWorkspace 等于在没有隔离的机器上放行一切,权限的 deny_rules 是 唯一护栏。真正的纵深防御是 DEFAULT / ACCEPT_EDITS + DockerWorkspace / E2BWorkspace, 详见 工作区与沙箱

  • 期望 EXPLORE 模式调用 check_permissionsEXPLORE 下引擎不调用工具的 check_permissions,只看 check_read_only 的返回值。因此 bypass_immune ASK 在 EXPLORE 下 不生效--只读判定就是终判。

  • 混淆 allow_rulesdeny_rulesPermissionContext 同时支持两者。deny_rules 在所有 模式下(包括 BYPASS)都生效,是硬护栏;allow_rulesDEFAULT 下放行匹配调用,但无法压制 bypass_immune=True 的安全 ASK。把安全关键规则放进 deny_rules,不要只靠 allow_rules

  • HITL 接受规则后不持久化 AgentState:用户在 HITL 中接受的 suggested_rule 会进当前 PermissionContext,但如果回复结束后没有把 AgentState 写回 RedisStorage,下次会话规则就丢了。 持久化模式见 状态与会话

先修

  • 中间件 - 理解包裹执行链的洋葱模型,权限检查发生在中间件链之后、工具执行之前

下一步

官方参考

学习文档整合站点