本文档说明 PolicyEngine 的工具管控、风险评估、审批缓存和成本熔断机制。策略引擎位于 ToolExecutor 之前,是 Harness 安全防线的核心。
设计融合了 Harness 加权风险评估:
- 三级工具管控:白名单(显式放行) > 黑名单(绝对拦截) > 风险评估(按需审批)
- 加权风险模型:数据敏感度、财务影响、不可逆性、置信度、用户授权五个维度的加权评分
- 三级权限:FREE(读操作) / ASK_FIRST(写操作每次问) / APPROVE_ONCE(高风险工具任务级授权)
- 审批缓存 TTL:相同 session+tool 的审批结果缓存 N 秒,减少重复询问
- 成本熔断器:累计成本超限后禁止 LLM 调用,支持 closed→open→half-open 三态恢复
ToolExecutor 准备执行工具
│
├─ PolicyEngine.evaluate_tool(tool_name, command, session_id, risk_factors)
│
├─ 1. 白名单检查
│ tool_name 在白名单 → 直接放行(return)
│
├─ 2. 黑名单检查
│ command 命中模式 → 拦截(allowed=False, risk_level=CRITICAL)
│
├─ 3. 权限级别
│ FREE → 直接放行
│ APPROVE_ONCE → 检查任务级授权标记
│ ASK_FIRST → 继续评估
│
├─ 4. 风险评估(加权因子模型)
│ RiskFactors → score(0-100) → RiskLevel(LOW/MEDIUM/HIGH/CRITICAL)
│
├─ 5. 审批缓存
│ cache_key = SHA256(session:tool:command)
│ 缓存命中 + 未过期 → 用缓存结果
│ 缓存未命中 → needs_approval=True
│
└─ 返回 PolicyDecision(allowed, needs_approval, risk_level, risk_score, cache_key)
class PermissionLevel(str, Enum):
FREE = "free" # 无需审批
ASK_FIRST = "ask_first" # 每次执行前询问
APPROVE_ONCE = "approve_once" # 任务级一次性授权| 权限 | 工具 | 原因 |
|---|---|---|
| FREE | read, grep, glob, memory, cache_read, approval_control | 只读/查询,无副作用 |
| ASK_FIRST | write, edit, bash, task | 修改文件系统 / 执行命令 |
| APPROVE_ONCE | (会话级一次性授权,第二次放行) | 任务级授权 |
第1次调用 docker run → needs_approval=True → 用户确认
└─ engine.grant_task_approval("s1", "docker")
第2次调用 docker ps → needs_approval=False(任务级已授权)
└─ session 内后续所有 docker 调用都放行
会话结束 → engine.reset_task_approvals("s1")
RiskFactors(
data_sensitivity: float = 0.0, # 是否访问敏感数据(0~1)
financial_impact: float = 0.0, # 是否产生费用(0~1)
irreversibility: float = 0.0, # 是否不可逆(0~1)
model_confidence: float = 0.8, # 模型置信度(0~1,越高越确定)
user_authorization: float = 1.0, # 用户授权级别(0~1)
)| 因子 | 权重 | 说明 |
|---|---|---|
| data_sensitivity | 0.30 | 操作是否涉及敏感数据路径(如 /etc、.env) |
| financial_impact | 0.25 | 是否可能产生额外费用(API 调用、资源创建) |
| irreversibility | 0.20 | 操作是否不可逆(删除、格式化、push --force) |
| model_confidence | 0.15 | 模型输出的置信度(低置信度 = 高风险) |
| user_authorization | 0.10 | 用户是否明确授权(如 ApproveOnce 已授权) |
| 分数 | 等级 | 处理 |
|---|---|---|
| 0~19 | LOW |
ASK_FIRST 也放行 |
| 20~49 | MEDIUM |
ASK_FIRST → 审批 |
| 50~79 | HIGH |
ASK_FIRST → 审批,APPROVE_ONCE → 审批 |
| 80~100 | CRITICAL |
黑名单命中 → 直接拦截 |
decision = engine.evaluate_tool("write", "write config.json", "s1")
# → needs_approval=True, cache_key="a1b2c3d4..."
# 用户确认后记录
engine.record_approval(decision.cache_key, approved=True)
# 5 分钟内再次调用
decision2 = engine.evaluate_tool("write", "write config.json", "s1")
# → needs_approval=False(缓存命中)缓存键:SHA256(f"{session_id}:{tool_name}:{command[:50]}")
TTL:可配置,默认 300 秒(通过 PolicyConfig.approval_cache_ttl_seconds)
CLOSED ──(累计超限)──→ OPEN ──(60s后)──→ HALF_OPEN ──(请求成功)──→ CLOSED
│ │
│ 拒绝所有 LLM 请求 │ 允许一次试探
│ │
└────────────────────┘
# 每次 LLM 调用前
allowed, reason, cumulative = engine.evaluate_cost(0.015)
if not allowed:
return f"成本熔断: {reason}" # → 前端提示
# 请求成功后
engine._circuit_breaker.on_success() # HALF_OPEN → CLOSED_targets_protected_path() 拦截对 任意 site-packages 目录下文件的写入操作,保护已安装的第三方库不被意外修改。
| 受保护 | 不受保护 |
|---|---|
/usr/lib/python3/site-packages/requests/api.py |
~/my-site-packages-backup/file.py |
~/.local/lib/site-packages/numpy/core.py |
site-packages-backup/foo |
X:/Python/Lib/site-packages/flask/app.py |
任意不含 site-packages 组件的路径 |
匹配规则:site-packages 必须作为完整路径组件出现(正则:(^|[/\\])site-packages($|[/\\])),避免误杀 my-site-packages-backup 等。
仅 edit、write、bash 三个写入类工具会触发检测。path / diff / command 参数被扫描,命中后直接返回 blocked。
_discover_protected_paths() 搜索常见 Python 安装位置(如 sys.prefix、~/.local、Homebrew 等)下的 site-packages 目录,但核心标记 "site-packages" 始终存在,确保即便未找到具体路径,模糊匹配仍然生效。
from core.policy_engine import PolicyEngine, RiskFactors
engine = PolicyEngine()
# 读文件 → FREE → 直接放行
d = engine.evaluate_tool("read", "read main.py", "s1")
assert d.allowed and not d.needs_approval
# 写文件 → ASK_FIRST → 每次审批
d = engine.evaluate_tool("write", "write config.json", "s1")
assert d.needs_approval
# 用户确认后
engine.record_approval(d.cache_key, approved=True)
# 危险命令 → 直接拦截
d = engine.evaluate_tool("bash", "rm -rf /", "s1")
assert not d.allowed # 被黑名单拦截# 高风险操作(删除生产数据)
factors = RiskFactors(
data_sensitivity=1.0,
financial_impact=0.8,
irreversibility=1.0,
model_confidence=0.5, # 模型不太确定
user_authorization=0.0, # 用户未授权
)
d = engine.evaluate_tool("bash", "DROP TABLE users", "s1", risk_factors=factors)
assert d.risk_level == RiskLevel.CRITICAL# 每次 LLM 调用前
allowed, reason, cumulative = engine.evaluate_cost(0.015)
if not allowed:
return f"成本熔断: 累计 {cumulative:.2f} 已超限"
# 会话结束时
engine.reset_cost()# 将某个 MCP 工具标记为 FREE
engine.set_tool_permission("mcp_read_only", PermissionLevel.FREE)
# 添加白名单(绕过黑名单)
engine.add_to_whitelist("trusted_admin_tool")
# 清除所有审批缓存
engine.clear_cache()# ToolExecutor 执行前
decision = policy_engine.evaluate_tool(
tool_name=event.tool_name,
command_or_action=event.command_or_action,
session_id=session_id,
risk_factors=assess_risk(event), # Orchestrator 构建因子
)
if not decision.allowed:
await send_blocked_notification(decision.blocked_reason)
return
if decision.needs_approval:
approved = await request_user_approval(decision)
if not approved:
return
policy_engine.record_approval(decision.cache_key, approved)