Security

Permission 权限系统

工具执行权限控制和审批流程

Permission 权限系统

Permission 系统提供了灵活的工具执行权限控制,支持三种审批模式和基于规则的智能决策。aster 1.x 版本引入了与 Claude Agent SDK 对齐的增强权限系统。

🆕 Claude Agent SDK 风格增强

aster 新增了 EnhancedInspector,提供以下增强功能:

  • CanUseTool 回调: 自定义权限检查逻辑
  • 沙箱集成: 与 SandboxSettings 深度集成
  • 会话级规则: 临时规则,会话结束自动清除
  • 动态权限更新: 运行时添加/移除规则
  • 违规记录: 记录和查询沙箱违规

🎯 核心概念

三种审批模式

模式说明适用场景
auto_approve自动批准所有操作开发测试、可信环境
smart_approve根据风险级别智能决策推荐默认模式
always_ask所有操作都需确认高安全性场景

风险级别

级别工具示例说明
LowRead, List, Search只读操作,无副作用
MediumWrite, Edit文件修改,有限范围
HighBash, Delete, Http系统命令,网络访问

📊 架构设计

graph TB
    Agent[Agent] --> Middleware[HITL Middleware]
    Middleware --> Inspector[Permission Inspector]

    Inspector --> Mode{审批模式}
    Mode -->|auto_approve| Auto[自动批准]
    Mode -->|smart_approve| Smart[智能审批]
    Mode -->|always_ask| Ask[总是询问]

    Smart --> Risk[风险评估]
    Risk --> Rules[规则匹配]
    Rules --> Decision{决策}

    Decision -->|Allow| Execute[执行工具]
    Decision -->|Deny| Reject[拒绝执行]
    Decision -->|Ask| Control[Control Channel]

    Control --> User[用户确认]
    User --> Execute

    style Inspector fill:#3b82f6
    style Smart fill:#10b981
    style Control fill:#f59e0b

🚀 快速开始

创建 Inspector

import "github.com/astercloud/aster/pkg/permission"

// 使用智能审批模式(推荐)
inspector, err := permission.NewInspector(
    permission.WithMode(permission.ModeSmartApprove),
)
if err != nil {
    log.Fatal(err)
}

检查权限

ctx := context.Background()

result, err := inspector.Check(ctx, &permission.Request{
    ToolName:  "Bash",
    Arguments: map[string]any{
        "command": "rm -rf /tmp/test",
    },
})

if result.NeedsApproval {
    // 需要用户确认
    fmt.Printf("需要审批: %s (风险: %s)\n", result.ToolName, result.RiskLevel)
} else if result.Decision == permission.DecisionAllow {
    // 自动批准
    fmt.Println("已批准执行")
} else {
    // 拒绝执行
    fmt.Printf("拒绝: %s\n", result.Reason)
}

🔧 审批模式详解

Auto Approve 模式

inspector, _ := permission.NewInspector(
    permission.WithMode(permission.ModeAutoApprove),
)

// 所有工具都自动批准
result, _ := inspector.Check(ctx, &permission.Request{
    ToolName: "Bash",
    Arguments: map[string]any{"command": "rm -rf /"},
})
// result.Decision == DecisionAllow
// result.NeedsApproval == false
⚠️ Auto Approve 模式会批准所有操作,仅在开发测试或完全可信的环境中使用。

Smart Approve 模式

inspector, _ := permission.NewInspector(
    permission.WithMode(permission.ModeSmartApprove),
)

// 只读操作 - 自动批准
result, _ := inspector.Check(ctx, &permission.Request{
    ToolName: "Read",
    Arguments: map[string]any{"path": "main.go"},
})
// result.NeedsApproval == false

// 写操作 - 需要审批
result, _ = inspector.Check(ctx, &permission.Request{
    ToolName: "Write",
    Arguments: map[string]any{"path": "main.go", "content": "..."},
})
// result.NeedsApproval == true

// 系统命令 - 需要审批
result, _ = inspector.Check(ctx, &permission.Request{
    ToolName: "Bash",
    Arguments: map[string]any{"command": "echo hello"},
})
// result.NeedsApproval == true

Always Ask 模式

inspector, _ := permission.NewInspector(
    permission.WithMode(permission.ModeAlwaysAsk),
)

// 即使只读操作也需要确认
result, _ := inspector.Check(ctx, &permission.Request{
    ToolName: "Read",
    Arguments: map[string]any{"path": "main.go"},
})
// result.NeedsApproval == true

📝 规则系统

添加规则

// 允许所有读取操作
inspector.AddRule(&permission.Rule{
    Pattern:   "Read",
    Decision:  permission.DecisionAllowAlways,
    RiskLevel: permission.RiskLevelLow,
    Note:      "允许所有读取操作",
})

// 禁止危险命令
inspector.AddRule(&permission.Rule{
    Pattern:   "Bash",
    Decision:  permission.DecisionDenyAlways,
    RiskLevel: permission.RiskLevelHigh,
    Conditions: []permission.Condition{
        {
            Field:    "command",
            Operator: "contains",
            Value:    "rm -rf",
        },
    },
    Note: "禁止危险的删除命令",
})

// 允许写入特定目录
inspector.AddRule(&permission.Rule{
    Pattern:   "Write",
    Decision:  permission.DecisionAllowAlways,
    Conditions: []permission.Condition{
        {
            Field:    "path",
            Operator: "prefix",
            Value:    "/tmp/",
        },
    },
    Note: "允许写入临时目录",
})

条件运算符

运算符说明示例
eq相等command eq "ls"
ne不等path ne "/etc/passwd"
contains包含command contains "rm"
prefix前缀path prefix "/home/"
suffix后缀path suffix ".txt"
regex正则command regex "^git\s+"

临时规则

// 创建临时规则(1小时后过期)
expiry := time.Now().Add(time.Hour)
inspector.AddRule(&permission.Rule{
    Pattern:   "Bash",
    Decision:  permission.DecisionAllowAlways,
    ExpiresAt: &expiry,
    Note:      "临时允许 Bash 命令",
})

规则持久化

// 保存规则到文件
err := inspector.SaveRules()
// 规则保存到: ~/.config/aster/permissions.json

// 创建时自动加载规则
inspector, _ := permission.NewInspector(
    permission.WithMode(permission.ModeSmartApprove),
    permission.WithAutoLoad(true),  // 自动加载已保存的规则
)

// 自定义规则文件路径
inspector, _ := permission.NewInspector(
    permission.WithPath("/custom/path/permissions.json"),
)

🔗 与 Agent 集成

使用 HITL 中间件

import (
    "github.com/astercloud/aster/pkg/middleware"
    "github.com/astercloud/aster/pkg/permission"
)

// 创建 Inspector
inspector, _ := permission.NewInspector(
    permission.WithMode(permission.ModeSmartApprove),
)

// 创建 HITL 中间件
hitlMiddleware := middleware.NewHumanInTheLoopMiddleware(&middleware.HumanInTheLoopMiddlewareConfig{
    Inspector: inspector,
    ApprovalHandler: func(ctx context.Context, req *middleware.ReviewRequest) ([]middleware.Decision, error) {
        // 自定义审批逻辑
        for _, action := range req.ActionRequests {
            fmt.Printf("需要审批: %s\n", action.ToolName)
            // 显示 UI 或命令行提示...
        }
        return []middleware.Decision{{Type: middleware.DecisionApprove}}, nil
    },
})

// 注册中间件
middleware.DefaultRegistry.Register("hitl", func(config *middleware.MiddlewareFactoryConfig) (middleware.Middleware, error) {
    return hitlMiddleware, nil
})

// 在 Agent 中使用
config := &types.AgentConfig{
    Middlewares: []string{"hitl"},
}

Control Channel 集成

// 订阅 Control Channel
controlCh := agent.Subscribe([]types.AgentChannel{types.ChannelControl}, nil)

go func() {
    for event := range controlCh {
        switch e := event.Event.(type) {
        case *types.ControlPermissionRequiredEvent:
            // 显示审批对话框
            approved := showApprovalDialog(e.ToolName, e.Arguments, e.RiskLevel)

            // 发送决定
            agent.RespondToPermission(e.RequestID, approved, "用户决定")
        }
    }
}()

📊 风险评估

内置风险规则

// 获取工具风险级别
risk := inspector.AssessRisk(&permission.Request{
    ToolName:  "Bash",
    Arguments: map[string]any{"command": "curl http://..."},
})
// risk == RiskLevelHigh

风险评估因素

因素低风险高风险
工具类型Read, ListBash, Delete
目标路径工作目录内系统目录
命令内容查询类修改/删除类
网络访问外部请求

💡 最佳实践

1. 分层权限策略

// 开发环境
if env == "development" {
    inspector, _ = permission.NewInspector(
        permission.WithMode(permission.ModeAutoApprove),
    )
}

// 生产环境
if env == "production" {
    inspector, _ = permission.NewInspector(
        permission.WithMode(permission.ModeAlwaysAsk),
    )
}

2. 白名单优先

// 添加安全工具白名单
safeTools := []string{"Read", "List", "Search", "Glob"}
for _, tool := range safeTools {
    inspector.AddRule(&permission.Rule{
        Pattern:  tool,
        Decision: permission.DecisionAllowAlways,
    })
}

3. 记录审批历史

// 记录所有审批决定
inspector.OnDecision(func(req *permission.Request, result *permission.Result) {
    log.Printf("Permission: tool=%s decision=%s reason=%s",
        req.ToolName, result.Decision, result.Reason)
})

🔐 EnhancedInspector (Claude Agent SDK 风格)

EnhancedInspector 是增强版权限检查器,整合了 CanUseTool 回调、沙箱配置和规则管理。

创建 EnhancedInspector

import (
    "github.com/astercloud/aster/pkg/permission"
    "github.com/astercloud/aster/pkg/types"
)

// 配置沙箱设置
sandboxConfig := &types.SandboxConfig{
    Kind: types.SandboxKindLocal,
    Settings: &types.SandboxSettings{
        Enabled:                  true,
        AutoAllowBashIfSandboxed: true,
        ExcludedCommands:         []string{"git", "docker"},
        AllowUnsandboxedCommands: true,
    },
    PermissionMode: types.SandboxPermissionDefault,
}

// 自定义权限回调
canUseTool := func(
    ctx context.Context,
    toolName string,
    input map[string]any,
    opts *types.CanUseToolOptions,
) (*types.PermissionResult, error) {
    // 阻止敏感文件访问
    if path, ok := input["path"].(string); ok {
        if path == "/etc/passwd" {
            return &types.PermissionResult{
                Behavior: "deny",
                Message:  "Access denied",
            }, nil
        }
    }
    return nil, nil // 让权限系统决策
}

// 创建增强检查器
inspector := permission.NewEnhancedInspector(&permission.EnhancedInspectorConfig{
    Mode:          permission.ModeSmartApprove,
    SandboxConfig: sandboxConfig,
    CanUseTool:    canUseTool,
    PersistPath:   ".aster/permissions.json",
    AutoLoad:      true,
})

CanUseTool 回调

type CanUseToolFunc func(
    ctx context.Context,
    toolName string,
    input map[string]any,
    opts *CanUseToolOptions,
) (*PermissionResult, error)

type CanUseToolOptions struct {
    Signal                 context.Context // 取消信号
    Suggestions            []PermissionUpdate // 建议的权限更新
    SandboxEnabled         bool // 沙箱是否启用
    BypassSandboxRequested bool // 是否请求绕过沙箱
}

type PermissionResult struct {
    Behavior           string            // "allow" | "deny"
    UpdatedInput       map[string]any    // 修改后的输入
    UpdatedPermissions []PermissionUpdate // 权限更新
    Message            string            // 拒绝原因
    Interrupt          bool              // 是否中断
}

权限检查流程

// 执行权限检查
call := &types.ToolCallSnapshot{
    ID:        "call-1",
    Name:      "Bash",
    Arguments: map[string]any{"command": "ls -la"},
}

result, err := inspector.Check(ctx, call)
if err != nil {
    log.Fatal(err)
}

if result.Allowed {
    fmt.Printf("✅ Allowed (decided by: %s)\n", result.DecidedBy)
} else if result.NeedsApproval {
    fmt.Printf("⏳ Needs approval (decided by: %s)\n", result.DecidedBy)
} else {
    fmt.Printf("🚫 Denied: %s\n", result.Message)
}

检查结果

type CheckResult struct {
    Allowed         bool   // 是否允许
    NeedsApproval   bool   // 是否需要审批
    DecidedBy       string // 决策来源
    Message         string // 消息
    Interrupt       bool   // 是否中断
    UpdatedInput    map[string]any // 修改后的输入
    ApprovalRequest *types.ControlPermissionRequiredEvent
}

决策来源 (DecidedBy)

说明
bypass_mode权限模式为 bypass
plan_mode权限模式为 plan
accept_edits_mode权限模式为 acceptEdits
canUseToolCanUseTool 回调决策
excluded_command命令在排除列表
sandbox_policy沙箱策略拒绝
auto_allow_bashAutoAllowBashIfSandboxed
rule:xxx匹配规则 xxx
low_risk低风险自动批准
medium_risk中风险需审批
high_risk高风险需审批

会话级规则

// 添加会话级规则(会话结束自动清除)
inspector.addSessionRule(permission.Rule{
    Pattern:  "Write",
    Decision: permission.DecisionAllow,
    Note:     "Session auto-approved",
})

// 清除会话级规则
inspector.ClearSessionRules()

动态权限更新

// 通过 CanUseTool 返回权限更新
return &types.PermissionResult{
    Behavior: "allow",
    UpdatedPermissions: []types.PermissionUpdate{
        {
            Type:        "addRules",
            Behavior:    "allow",
            Destination: "session", // 或 "project", "user"
            Rules: []types.PermissionRule{
                {ToolName: "Write", RuleContent: "auto-approved"},
            },
        },
    },
}, nil

违规记录

// 记录违规
inspector.RecordViolation(types.SandboxViolation{
    Type:      "file",
    Path:      "/etc/passwd",
    Operation: "read",
    Blocked:   true,
    Timestamp: time.Now().Unix(),
    Details:   "Attempted to read system file",
})

// 获取违规记录
violations := inspector.GetViolations()
for _, v := range violations {
    fmt.Printf("Violation: %s %s (%s)\n", v.Type, v.Path, v.Operation)
}

🎛️ 权限模式 (SandboxPermissionMode)

模式常量说明
默认SandboxPermissionDefault标准权限检查流程
接受编辑SandboxPermissionAcceptEdits自动接受文件编辑
绕过权限SandboxPermissionBypass绕过所有权限检查
规划模式SandboxPermissionPlan只记录不执行
sandboxConfig := &types.SandboxConfig{
    PermissionMode: types.SandboxPermissionAcceptEdits,
}

⚠️ dangerouslyDisableSandbox

模型可以在工具输入中请求绕过沙箱:

// 工具调用参数
{
    "command": "sudo apt update",
    "dangerouslyDisableSandbox": true
}

处理流程:

  1. 检查 AllowUnsandboxedCommands 配置
  2. 如果不允许,直接拒绝
  3. 如果允许,提升风险级别,需要用户审批

📚 相关文档

🔗 示例代码

# 运行 Permission 示例
go run ./examples/permission/

# 运行 Sandbox Permission 示例 (Claude Agent SDK 风格)
go run ./examples/sandbox-permission/

# 运行 Human-in-the-Loop 示例
go run ./examples/human-in-the-loop/