Skip to content
Figo Blogs
Go back

Permission & HITL:Agent 的权限、审批与策略控制

Contents

Table of contents

Open Table of contents

1. 为什么 Agent 权限不是一个布尔开关

最简单的实现可能只有:

if dangerous:
    ask_user()

但真实系统会遇到:

读文件可以自动允许
改工作区代码可以自动允许
改生产配置要审批
联网 GET 可以允许
联网 POST 可能审批
git status 可以允许
git reset --hard 风险很高
rm -rf 更高风险
创建云资源可能产生费用
发送邮件会产生不可逆外部副作用

因此权限不能只有:

ALLOW / DENY

还需要:

自动允许
条件允许
需要审批
临时授权
范围授权
永久策略
强制拒绝

2. 权限治理的四层模型

四层职责:

层解决的问题
Capability系统是否具备这种能力
Policy根据规则,这类动作是否被允许
Approval本次动作是否需要人确认
Execution在既定约束下真正执行

3. Capability ≠ Permission

例如系统注册了:

bash
edit_file
deploy
send_email

这只代表:

Agent 有能力调用

不代表:

当前 Session 可以执行

例如:

deploy

可能在开发环境允许,在生产环境必须审批。

因此 Tool Schema 暴露给模型和实际执行权限要分开。


4. Permission Engine 的职责

Permission Engine 输入:

Tool
Arguments
Effect Metadata
Session State
User Policy
Workspace Policy
Risk Profile
Environment

输出:

ALLOW
DENY
REQUIRE_APPROVAL
ALLOW_WITH_CONSTRAINTS

推荐抽象:

@dataclass
class PermissionDecision:
    decision: str
    reason: str

    constraints: dict
    risk_level: str

    approval_required: bool
    cacheable: bool

5. Risk Classification:先识别动作风险

一个 Tool 本身的风险并不固定。

例如:

bash("ls")

和:

bash("rm -rf /")

都是 bash,风险完全不同。

所以不能只按:

tool_name

分类。

应该结合:

Tool Type
Arguments
Target Resource
Environment
External Side Effect
Reversibility
Cost
Data Sensitivity

6. 推荐风险维度

可以至少考虑:

Read / Write
Local / External
Reversible / Irreversible
Cheap / Cost-incurring
Workspace / System-wide
User-owned / Shared
Development / Production
No Side Effect / External Side Effect

组合后得到:

LOW
MEDIUM
HIGH
CRITICAL

7. 一个实用的风险矩阵

行为风险示例
读取工作区文件LOW
grep / LSP / git statusLOW
修改普通源码MEDIUM
删除工作区文件HIGH
修改生产配置HIGH
git reset —hardHIGH
发布生产环境CRITICAL
发送邮件 / 创建订单HIGH
创建付费云资源CRITICAL
Note

风险级别不是通用常数,应由实际产品和环境策略定义。


8. Policy:把“是否允许”写成规则

Policy 可以来源于:

System Policy
Organization Policy
Project Policy
User Policy
Session Policy
Tool-specific Policy

推荐优先级:

System / Safety Policy
↓
Organization Policy
↓
Project Policy
↓
User Preferences
↓
Session Overrides

低优先级规则不能覆盖高优先级约束。


9. Policy 不应该只有字符串判断

脆弱方式:

if "rm -rf" in command:
    deny()

更合理:

Parse
Classify
Evaluate

例如 Shell:

Command
↓
Parse Command Structure
↓
Detect Paths / Network / Package / Process Effects
↓
Risk Classification
↓
Policy Decision

10. Policy Decision Flow


11. ALLOW_WITH_CONSTRAINTS

真实系统常常不是简单 Allow。

例如:

允许 bash
但:
- cwd 必须在 workspace
- 禁止网络
- 只允许写 tmp/
- timeout 30 秒

所以 Permission Decision 可以附带:

writable_roots
network_access
max_runtime
allowed_domains
allowed_commands

然后交给 Execution Runtime / Sandbox 落实。


12. Approval:人类确认的真正意义

Human Approval 解决的不是:

模型是否正确

而是:

用户是否愿意承担这个副作用。

例如:

删除文件
发送邮件
创建资源
发布生产

即使模型判断完全正确,也应该由用户确认。


13. Approval 应显示什么

糟糕的审批:

Allow bash?

好的审批应该说明:

What:
  删除 3 个文件

Where:
  ./build/cache/*

Why:
  清理旧构建缓存

Risk:
  文件删除,不可通过 Tool 自动恢复

Scope:
  仅本次

核心原则:

审批必须让用户理解将发生什么副作用。


14. Approval UI 的信息模型

至少包含:

Action
Target
Effect
Reason
Risk
Scope
Reversibility
Estimated Cost(如适用)

而不是把原始 Tool JSON 全部丢给用户。


15. Approval Scope:用户到底批准了什么

用户点击:

Allow

可能代表不同范围:

Allow once
Allow this exact command
Allow this tool for session
Allow writes under this path
Allow this domain
Always allow this policy class

这些 Scope 必须显式。


16. 推荐 Approval Scope

ONCE
EXACT_ACTION
RESOURCE_SCOPE
TOOL_SCOPE
SESSION_SCOPE
PROJECT_SCOPE
PERSISTENT_POLICY

越宽的 Scope 风险越高。


17. Approval Caching

如果 Agent 每次:

read_file

都弹确认,会无法使用。

因此低风险 Approval 可以缓存。

例如用户批准:

允许本 Session 修改 ./src/**

后续相同范围内:

edit src/a.ts
edit src/b.ts

不再重复询问。


18. Approval Cache Key

不能只:

tool_name

作为缓存键。

更合理:

tool
effect
resource_scope
argument_pattern
session
policy_version

否则一次:

bash("ls")

审批可能错误扩展成:

bash("rm -rf /")

19. Approval Cache 失效

当发生:

Session 改变
Workspace 改变
Target Resource 超出范围
Risk Level 提升
Policy 更新
User Steering

旧 Approval 应失效。


20. Least Privilege:默认最小权限

核心原则:

只给完成当前任务所需的最小能力。

例如当前任务:

修复 TypeScript Bug

需要:

read repo
edit src
run tests

通常不需要:

访问生产
发邮件
创建云资源

所以这些能力可以根本不暴露,或明确 Deny。


21. Capability Exposure 本身就是权限控制

上一章提到 Dynamic Tool Exposure。

Permission 可以更进一步:

不需要的 Capability
↓
不暴露给模型

这比:

暴露后再拒绝

更简单、安全,也减少 Context Token。


22. Denied Observation:拒绝后 Agent 应该怎样继续

Permission Denied 不应该:

raise FatalError

而应该进入模型观察。

例如:

PERMISSION_DENIED

Operation:
  delete_file("config/prod.yaml")

Reason:
  Production configuration is protected.

Allowed alternatives:
  - read_file
  - propose_patch
  - request explicit approval

模型可以:

换方案
请求审批
只生成建议
停止执行

23. 不要鼓励模型反复申请同一权限

如果用户明确拒绝:

不要删除这个文件

模型不能下一轮又:

请允许我删除这个文件

因此 Denial 应进入 Session State。

可以记录:

Denied Intent
Resource
Scope
Reason

并在后续 Tool Call 前快速拦截。


24. Permission Denial 也可以形成 Steering

用户拒绝动作往往隐含新要求。

例如:

不允许删除数据库,换迁移方案。

这不仅是:

DENY

还包含:

Latest User Intent

因此可以同时产生:

PermissionDenied
SteeringReceived

25. HITL 不只有 Approval

Human-in-the-loop 可以分成:

Approval
Clarification
Steering
Review
Escalation
Takeover

Agent 系统不应该把所有人机交互都建模成“Allow / Deny”。


26. HITL 的几种模式

Approval

是否允许执行动作?

Clarification

目标不明确,需要用户决策

Steering

用户改变方向

Review

执行前让用户检查 Diff / Plan

Escalation

Agent 无法安全决策,交给人

Takeover

用户接管操作

27. Approval 与 Clarification 必须区分

例如:

“要修改哪个环境?”

这是 Clarification。

而:

“是否允许修改生产环境?”

这是 Approval。

前者解决:

目标不确定

后者解决:

风险承担

28. Permission Pending 是 Session State

如果 Agent 等待审批:

Session = WAITING_APPROVAL

而不是:

Agent Loop 继续跑

推荐状态:


29. Approval 必须绑定原始 Action

用户批准后不能:

模型重新生成一个更危险命令

然后沿用刚才 Approval。

Approval 应绑定:

call_id
arguments_hash
effect
resource_scope

如果 Tool Call 改变:

重新评估

30. TOCTOU:审批后执行前对象变化

一个经典问题:

用户批准删除 ./tmp/a
↓
文件路径被替换成 symlink
↓
实际删除其他位置

这属于 Time-of-check to Time-of-use 问题。

Permission 决策和执行之间需要尽量保持:

Resource Identity
Preconditions
Sandbox Boundary

而不仅仅相信字符串路径。


31. Permission 与 Sandbox 的区别

Permission 回答:

应该允许什么?

Sandbox 回答:

即使 Tool 有 Bug 或模型绕过逻辑,它实际上最多能做什么?

所以:

Permission = Logical Governance
Sandbox = Enforcement Boundary

两者必须同时存在。


32. Permission 与 Tool Runtime 的区别

Tool Runtime 负责:

Validation
Scheduling
Execution
Retry
Result

Permission 负责:

Allow / Deny / Approve / Constraints

因此:

Tool Runtime 调用 Permission Engine

但 Permission Engine 不负责真正执行 Tool。


33. Permission 与 Policy Hooks

Hooks 很适合做:

PreToolUse
PermissionRequest
PostToolUse

例如:

Tool Call
↓
PreToolUse Hook
↓
Policy Engine
↓
Approval
↓
Execution

但 Hook 只是扩展点,真正的权限模型仍需清晰定义。


34. Risk Escalation

一个动作可能开始是低风险,但参数变化后升级。

例如:

read_file("./src/a.ts")

低风险。

变成:

read_file("/etc/shadow")

风险提升。

所以权限不能只在:

Tool 注册时

评估,而要对每次 Call 做评估。


35. Cost Governance

某些 Tool 的风险不是数据,而是成本。

例如:

创建 GPU 实例
调用昂贵 API
运行长时间 Benchmark

Policy 可以加入:

estimated_cost
budget_remaining

超过阈值:

REQUIRE_APPROVAL

36. Network Permission

网络访问最好拆分:

NO_NETWORK
READ_ONLY_NETWORK
DOMAIN_ALLOWLIST
FULL_NETWORK

例如:

访问官方文档

和:

POST 数据到未知服务器

不应该是同一权限级别。


37. Filesystem Permission

推荐至少拆:

READ_ROOTS
WRITE_ROOTS
PROTECTED_PATHS

例如:

read:
  workspace/**

write:
  workspace/src/**
  workspace/tests/**

protected:
  workspace/.git/**
  /etc/**

这部分最终需要 Sandbox 落实。


38. Shell Permission

Shell 是最复杂的 Capability。

因为一个:

bash

可以间接完成:

文件修改
网络访问
进程启动
包安装
删除
Git 操作
系统配置

因此 Shell Permission 通常需要:

Command Parsing
Effect Classification
Working Directory Policy
Network Policy
Filesystem Sandbox
Approval

39. Command Approval 不能只看字符串

例如:

python script.py

表面无害,但:

script.py

可能删除文件或联网。

所以 Shell 风险评估只能部分静态判断。

真正安全性仍要依赖:

Sandbox

这再次说明:

Permission 不能替代 Sandbox。


40. Plan Approval

复杂任务中,可以批准:

Plan

而不是每个 Tool Call。

例如:

计划:
1. 修改 src/auth.ts
2. 修改 tests/auth.test.ts
3. 运行 pytest

用户批准后:

允许在明确 Scope 内执行

但如果 Agent 后续越界:

删除数据库

仍需重新审批。


41. Scoped Autonomy

这是工业 Agent 很重要的产品模式:

用户不是每一步都审批,而是先授予一个受限自治范围。

例如:

你可以:
- 修改 ./src/**
- 修改 ./tests/**
- 运行本地测试

你不能:
- git push
- 网络部署
- 修改 infra/prod/**

这比:

每次 Tool 弹窗

体验更好。


42. Session Permission Profile

可以保存:

filesystem:
  read:
    - "./**"
  write:
    - "./src/**"
    - "./tests/**"

shell:
  local_commands: allow
  network: deny

git:
  commit: approval
  push: deny

external:
  deploy: deny

Session 启动时确定。


43. Policy-as-Code

复杂组织环境可以把 Permission Policy 变成:

可版本化规则

例如:

rules:
  - match:
      tool: edit_file
      path: "src/**"
    decision: allow

  - match:
      tool: edit_file
      path: "infra/prod/**"
    decision: require_approval

优势:

可审计
可测试
可版本控制

44. Permission Policy 也需要测试

可以建立 Policy Test:

Case:
  tool = edit_file
  path = src/a.ts
Expected:
  ALLOW
Case:
  tool = bash
  command = git push
Expected:
  REQUIRE_APPROVAL

避免策略升级后产生意外放权。


45. Permission Events 应进入 Session

推荐:

PermissionEvaluated
PermissionRequested
PermissionGranted
PermissionDenied
PermissionExpired
PermissionScopeCreated

这样:

Resume
Audit
Replay
Eval

都有事实基础。


46. Approval Replay 的边界

State Replay 可以恢复:

用户当时批准了什么

但 Execution Replay 时:

旧 Approval 不一定应该自动重用

尤其:

环境变化
资源变化
Policy 版本变化

可能需要重新审批。


47. Audit:谁批准了什么

高风险 Agent 必须能够回答:

谁
什么时候
批准了什么
作用范围
基于哪个 Policy
最终执行结果

这比只记录:

approved = true

更重要。


48. Permission Trace

一次高风险执行可以记录:

ToolCall
├── risk = HIGH
├── policy = prod-write-v3
├── decision = REQUIRE_APPROVAL
├── approval_scope = EXACT_ACTION
├── approved_by = user
├── approval_event_id = ...
├── sandbox_profile = restricted
└── result = SUCCESS

49. Permission Metrics

推荐:

指标说明
Auto-Allow Rate自动执行比例
Approval Request Rate人工介入频率
Approval Grant Rate用户同意比例
Denied Recovery Rate拒绝后 Agent 是否能换方案
Repeat Approval Rate是否频繁重复询问
Scope Escalation Rate是否经常扩大权限
Policy Violation Rate越权尝试比例
False Positive Approval不必要审批
False Negative Risk高风险动作未拦截
Approval Latency人等待造成的时延

50. Permission UX 的两个极端

过度审批

每个 read
每个 grep
每个 test

都询问。

结果:

用户疲劳
机械点击 Allow
安全性反而下降

这叫 Approval Fatigue。


过度自动化

全部 Allow

则风险失控。

最佳目标:

Low-risk Autonomy + High-risk Explicit Control


51. Approval Fatigue

一旦用户习惯:

Allow
Allow
Allow
Allow

真正危险操作出现时也可能直接批准。

所以权限系统质量的重要指标不是:

弹窗越多越安全

而是:

每一次审批都足够有意义

52. Risk-adaptive Approval

可以根据风险:

LOW
→ auto allow

MEDIUM
→ scoped allow / session policy

HIGH
→ explicit approval

CRITICAL
→ approval + stronger confirmation

避免一刀切。


53. Escalation:Agent 不确定时应该交给人

例如:

无法确定操作是否会删除用户数据

不要让模型硬猜。

可以:

ESCALATE

附带:

已知事实
风险
可选方案
推荐

交给用户决定。


54. Human Review

一些动作最好不是简单 Allow,而是:

Review Diff

例如:

修改 20 个文件

让用户看到:

Diff Summary
Risk
Tests

再批准 Commit / Deploy。


55. Permission 与 Commit / Push 分层

Coding Agent 中常见:

Edit
Commit
Push
Deploy

风险逐层上升。

可以:

Edit workspace
→ auto

Commit
→ maybe auto / approval

Push
→ approval

Deploy production
→ explicit approval

这比统一:

Git = Allow

更合理。


56. Deny-by-default 还是 Allow-by-default

对于开发工作区:

低风险操作

可以 Allow-by-default。

对于:

生产
外部系统
高价值数据

通常 Deny-by-default 更合理。

因此权限模式应该按:

Environment Trust Level

变化。


57. Permission Failure Taxonomy

推荐:

CAPABILITY_NOT_EXPOSED
POLICY_DENIED
USER_DENIED
APPROVAL_EXPIRED
SCOPE_MISMATCH
RESOURCE_OUT_OF_SCOPE
RISK_ESCALATED
POLICY_VERSION_MISMATCH
APPROVAL_STALE

方便 Eval 分析。


58. Permission 与 Steering 的交界

当用户说:

不要执行任何网络请求

这既是:

Steering

也是:

Session Permission Policy Update

因此需要:

更新 Goal / Constraint
+
更新 Permission Profile

59. Permission 与 Context 的交界

模型需要知道:

哪些能力不可用

但不需要知道全部内部 Policy 实现。

Context 可以投影:

Network access is disabled for this session.
Do not attempt network tools.

而 Session 保存完整:

Policy Rule
Decision
Scope
Reason

60. Permission 与 Subagent

未来 Subagent 不能默认继承父 Agent 全部权限。

更合理:

Parent Permission Profile
↓
Subtask Required Capability
↓
Scoped Delegated Permission
↓
Subagent

即:

权限应该显式委托,而不是隐式继承。


61. Permission 与 Long-running Agent

长期 Agent 中 Approval 可能过期。

例如:

用户上午批准修改 staging
↓
Agent 晚上才执行

环境可能已变化。

所以 Approval 可以带:

expires_at
state_precondition
resource_version

超时后重新评估。


62. 推荐 Permission Engine 架构

这张图可以作为本章最终架构模型。


63. 推荐 Permission 伪代码

class PermissionEngine:
    def __init__(
        self,
        policy_engine,
        risk_classifier,
        approval_manager,
    ):
        self.policy = policy_engine
        self.risk = risk_classifier
        self.approval = approval_manager

    async def evaluate(
        self,
        tool,
        arguments,
        session,
    ):
        effect = tool.analyze_effect(arguments)

        risk = self.risk.classify(
            tool=tool,
            arguments=arguments,
            effect=effect,
            session=session,
        )

        policy_result = self.policy.evaluate(
            tool=tool,
            arguments=arguments,
            effect=effect,
            risk=risk,
            session=session,
        )

        if policy_result.decision == "DENY":
            return PermissionDecision(
                decision="DENY",
                reason=policy_result.reason,
                constraints={},
                risk_level=risk.level,
                approval_required=False,
                cacheable=False,
            )

        if policy_result.decision == "ALLOW":
            return PermissionDecision(
                decision="ALLOW",
                reason=policy_result.reason,
                constraints=policy_result.constraints,
                risk_level=risk.level,
                approval_required=False,
                cacheable=policy_result.cacheable,
            )

        approval = await self.approval.request(
            action=tool.describe_action(arguments),
            risk=risk,
            proposed_scope=policy_result.approval_scope,
        )

        if approval.denied:
            return PermissionDecision(
                decision="DENY",
                reason=approval.reason,
                constraints={},
                risk_level=risk.level,
                approval_required=True,
                cacheable=False,
            )

        return PermissionDecision(
            decision="ALLOW",
            reason="Approved by user",
            constraints=approval.constraints,
            risk_level=risk.level,
            approval_required=True,
            cacheable=approval.cacheable,
        )

64. 推荐实践实验

实验 1:Low-risk Auto Allow

允许:

read_file
grep
git status

不弹审批。

观察交互是否流畅。


实验 2:Protected Path

策略:

src/**
→ allow edit

infra/prod/**
→ require approval

验证路径切换后 Risk / Policy 是否正确变化。


实验 3:Denied Observation

Agent 尝试:

delete_file(...)

用户拒绝。

验证下一轮模型是否:

不重复申请
换安全方案

实验 4:Approval Scope

用户批准:

edit ./src/**

验证:

src/a.ts
→ allow

infra/a.ts
→ re-evaluate

实验 5:Approval Cache

同一 Session 连续编辑 5 个 src/** 文件。

确认:

不会重复弹 5 次

实验 6:Risk Escalation

先:

bash("ls")

后:

bash("git reset --hard")

确认第二个不能继承第一个 Approval。


实验 7:Steering Update

用户说:

从现在开始不要联网。

确认:

Context Constraint
+
Permission Profile

同时更新。


实验 8:Plan Approval

让 Agent 提交:

修改文件范围 + 测试计划

用户批准后,允许在 Scope 内连续执行。

超出 Scope 时重新审批。


65. Permission Eval

建议记录:

指标说明
Auto-Allow Rate自治比例
Approval Rate人工介入频率
Approval Grant Rate用户批准比例
Denied Recovery Rate被拒绝后恢复能力
Repeat Denial Rate是否反复请求同一拒绝动作
Approval Cache HitScope 设计质量
Risk Escalation Catch Rate风险升级是否拦截
Policy Violation Attempts越权尝试
Approval Fatigue Proxy单任务审批次数
Unsafe Auto-Allow错误自动放权
Unnecessary Approval低风险误拦截
Approval Latency人机等待时延

66. 本章必答的 22 个问题

  • Capability 与 Permission 有什么区别?
  • Policy 与 Approval 有什么区别?
  • 为什么不能只按 Tool Name 判断风险?
  • Risk Classification 应考虑哪些维度?
  • 什么是 ALLOW_WITH_CONSTRAINTS?
  • Human Approval 真正解决什么问题?
  • Approval UI 为什么必须显示副作用和 Scope?
  • Approval Scope 有哪些层级?
  • Approval Cache 为什么不能只按 Tool Name?
  • 什么情况下旧 Approval 应失效?
  • Least Privilege 如何落到 Agent Tool Exposure?
  • Permission Denied 为什么应该成为 Observation?
  • 为什么用户拒绝后不能反复申请相同动作?
  • Approval 与 Clarification 有什么区别?
  • Permission Pending 为什么应该是 Session State?
  • Approval 为什么必须绑定原始 Action?
  • 什么是 TOCTOU?
  • Permission 与 Sandbox 的职责边界是什么?
  • 为什么 Shell Permission 特别难?
  • 什么是 Scoped Autonomy?
  • Approval Fatigue 为什么会降低安全性?
  • Subagent 为什么不应该自动继承父 Agent 全部权限?

67. 与下一篇 Sandbox & Execution Runtime 的衔接

到这里,我们已经解决:

Agent 想做什么
↓
是否允许做

但还没有解决:

即使 Policy 错了、Tool 有 Bug、模型绕过预期逻辑,真实执行环境如何保证它仍然不能越界?

这就是 Sandbox。

下一篇:

Sandbox & Execution Runtime:Agent 如何安全获得 Shell 权限

会重点研究:

Filesystem Isolation
Writable Roots
Network Isolation
Process Tree
Resource Limits
Working Directory
Environment Filtering
OS-specific Enforcement
Container / Namespace
Seatbelt / Bubblewrap 等机制的抽象层
Cancellation
Sandbox Profiles

68. 一句话总纲

Quote

工业级 Agent Permission 的本质,不是“弹一个确认框”,而是把 Capability、Risk、Policy、Approval Scope、Least Privilege 与 Runtime Constraints 组织成一套可持续治理机制:低风险操作获得受限自治,高风险副作用必须显式确认,而任何拒绝、授权和权限变更都必须成为 Session 中可恢复、可审计的事实。


Share this post:

Previous Post
JavaScript 与 TypeScript Web 生态知识地图
Next Post
Coding Agent Editing Runtime:Read、Search、Edit、Diff、LSP 与 Test