aster的沙箱系统提供安全隔离的代码执行环境,确保Agent执行的命令不会危害主机系统。本系统参考 Claude Agent SDK 设计,提供细粒度的权限控制和安全策略。
aster 1.x 版本引入了与 Claude Agent SDK 对齐的沙箱和权限系统:
Agent可能会:
rm -rf /)┌────────────────────────────────────┐
│ 主机系统 │
│ ┌──────────────────────────────┐ │
│ │ aster Runtime │ │
│ │ │ │
│ │ ┌────────────────────────┐ │ │
│ │ │ Sandbox │ │ │
│ │ │ ┌──────────────────┐ │ │ │
│ │ │ │ Agent执行代码 │ │ │ │
│ │ │ │ (隔离环境) │ │ │ │
│ │ │ └──────────────────┘ │ │ │
│ │ │ - 限制文件访问 │ │ │
│ │ │ - 限制网络访问 │ │ │
│ │ │ - 限制资源使用 │ │ │
│ │ └────────────────────────┘ │ │
│ └──────────────────────────────┘ │
└────────────────────────────────────┘
aster支持三种沙箱执行模式,满足不同场景需求:
graph TB
subgraph 模式1: 会话级沙箱 - aster默认
A1[客户端发起任务] --> B1[Agent创建]
B1 --> C1[沙箱初始化]
C1 --> D1[任务1执行]
D1 --> E1[任务2执行]
E1 --> F1[任务N执行]
F1 --> G1[Agent关闭]
G1 --> H1[沙箱销毁]
end
subgraph 模式2: 任务级沙箱 - Manus模式
A2[客户端发起任务1] --> B2[创建沙箱1]
B2 --> C2[执行任务1]
C2 --> D2[销毁沙箱1]
E2[客户端发起任务2] --> F2[创建沙箱2]
F2 --> G2[执行任务2]
G2 --> H2[销毁沙箱2]
end
subgraph 模式3: 沙箱池模式 - 企业级优化
P1[沙箱池预热] --> P2[沙箱1-待命]
P1 --> P3[沙箱2-待命]
P1 --> P4[沙箱N-待命]
T1[任务1] --> P2
T2[任务2] --> P3
P2 --> R1[执行完成-归还池]
P3 --> R2[执行完成-归还池]
end
style 模式1 fill:#e1f5e1
style 模式2 fill:#ffe1e1
style 模式3 fill:#e1e5ff
| 模式 | 沙箱生命周期 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| 会话级 | Agent创建→关闭 | 长对话、状态保留 | 无重复创建开销 保留执行上下文 | 长期占用资源 |
| 任务级 | 每个任务独立 | 高安全要求、无状态任务 | 完全隔离 无状态污染 | 冷启动延迟 成本较高 |
| 沙箱池 | 预热+复用 | 高并发、低延迟 | 极低延迟 资源利用率高 | 需要状态清理 管理复杂 |
aster支持多种沙箱后端:
| 沙箱类型 | 隔离级别 | 使用场景 | 性能 | 成本 |
|---|---|---|---|---|
| LocalSandbox | 进程级 | 开发测试 | 高 | 免费 |
| AliyunSandbox | 容器级 | 生产环境 | 中 | 按用量 |
| VolcengineSandbox | 容器级 | 生产环境 | 高 | 按用量 |
| MockSandbox | 无隔离 | 单元测试 | 极高 | 免费 |
ag, err := agent.Create(ctx, &types.AgentConfig{
Sandbox: &types.SandboxConfig{
Kind: types.SandboxKindLocal,
WorkDir: "./workspace",
},
}, deps)
ag, err := agent.Create(ctx, &types.AgentConfig{
Sandbox: &types.SandboxConfig{
Kind: types.SandboxKindLocal,
WorkDir: "/workspace",
Config: map[string]interface{}{
"use_docker": true,
"image": "golang:1.21",
"memory": "512m",
"cpu": "1.0",
},
},
}, deps)
ag, err := agent.Create(ctx, &types.AgentConfig{
Sandbox: &types.SandboxConfig{
Kind: types.SandboxKindAliyun,
WorkDir: "/workspace",
Config: map[string]interface{}{
"region": "cn-hangzhou",
"access_key": os.Getenv("ALIYUN_ACCESS_KEY_ID"),
"secret_key": os.Getenv("ALIYUN_ACCESS_KEY_SECRET"),
// 可选配置
"timeout": 300, // 超时时间(秒)
"memory_limit": 1024, // 内存限制(MB)
"cpu_limit": 2.0, // CPU限制(核心数)
"network_mode": "restricted", // 网络模式
},
},
}, deps)
# .env文件
export ALIYUN_ACCESS_KEY_ID="your-access-key"
export ALIYUN_ACCESS_KEY_SECRET="your-secret-key"
export ALIYUN_REGION="cn-hangzhou"
graph TB
subgraph 客户端环境
APP[应用程序] --> SDK[aster Runtime]
end
subgraph aster Runtime
SDK --> AGENT[Agent实例]
AGENT --> MW[中间件栈]
AGENT --> EXEC[工具执行器]
end
subgraph 阿里云
EXEC -->|HTTPS API| AGENTBAY[AgentBay服务]
AGENTBAY --> FC[函数计算]
FC --> CONTAINER1[容器实例1]
FC --> CONTAINER2[容器实例2]
FC --> CONTAINERN[容器实例N]
CONTAINER1 --> FS1[文件系统]
CONTAINER1 --> NET1[网络]
FS1 --> OSS[OSS对象存储]
NET1 --> VPC[VPC网络]
end
subgraph 安全边界
AGENTBAY --> IAM[访问控制<br/>AccessKey/SecretKey]
FC --> LIMIT[资源限制<br/>CPU/内存/磁盘]
NET1 --> FW[安全组<br/>流量控制]
end
style 客户端环境 fill:#e1f5e1
style aster Runtime fill:#fff4e1
style 阿里云 fill:#e1e5ff
style 安全边界 fill:#ffe1e1
ag, err := agent.Create(ctx, &types.AgentConfig{
Sandbox: &types.SandboxConfig{
Kind: types.SandboxKindVolcengine,
WorkDir: "/workspace",
Config: map[string]interface{}{
"region": "cn-beijing",
"access_key_id": os.Getenv("VOLC_ACCESS_KEY_ID"),
"secret_access_key": os.Getenv("VOLC_SECRET_ACCESS_KEY"),
// 可选配置
"instance_type": "standard", // standard/performance
"timeout": 600,
"auto_destroy": true,
},
},
}, deps)
mockSandbox := sandbox.NewMockSandbox()
// 预设命令返回
mockSandbox.SetOutput("ls", "file1.txt\nfile2.txt\n")
mockSandbox.SetOutput("cat README.md", "# Hello\n...")
ag, err := agent.Create(ctx, &types.AgentConfig{
Sandbox: &types.SandboxConfig{
Kind: types.SandboxKindMock,
},
}, &agent.Dependencies{
SandboxFactory: func() sandbox.Sandbox {
return mockSandbox
},
// ...
})
func TestAgentWithMockSandbox(t *testing.T) {
mock := sandbox.NewMockSandbox()
mock.SetOutput("go test", "PASS\nok\t...")
ag, _ := agent.Create(ctx, config, &agent.Dependencies{
SandboxFactory: func() sandbox.Sandbox {
return mock
},
})
result, _ := ag.Chat(ctx, "运行测试")
assert.Contains(t, result.Text, "测试通过")
}
下图展示了从客户端发起请求到沙箱执行完成的完整交互流程:
sequenceDiagram
autonumber
participant C as 客户端
participant A as Agent
participant M as 中间件栈
participant E as 工具执行器
participant S as 沙箱
participant R as 云端沙箱<br/>(Aliyun/Volcengine)
C->>A: Chat("列出当前目录文件")
activate A
A->>M: WrapModelCall()
activate M
Note over M: 文件系统中间件<br/>子Agent中间件<br/>总结中间件
M->>A: LLM响应(需要调用bash工具)
deactivate M
A->>E: 调用bash工具
activate E
E->>E: 解析参数<br/>构造Command
alt 本地沙箱
E->>S: Execute(ls -la)
activate S
S->>S: 进程隔离执行
S-->>E: Result{Stdout, ExitCode}
deactivate S
else 云端沙箱
E->>R: 发起远程调用
activate R
R->>R: 容器启动(~2s)
R->>R: 执行命令
R-->>E: Result{Stdout, ExitCode}
deactivate R
end
E-->>A: 工具执行结果
deactivate E
A->>M: WrapModelCall(带工具结果)
activate M
M->>A: LLM最终响应
deactivate M
A-->>C: 流式返回结果
deactivate A
| 阶段 | 本地沙箱耗时 | 云端沙箱耗时 | 说明 |
|---|---|---|---|
| 1-3 LLM推理 | ~500ms | ~500ms | 第一次模型调用 |
| 4-6 工具准备 | ~10ms | ~10ms | 参数解析和验证 |
| 7-9 沙箱执行 | ~50ms | ~2s | 云端需要冷启动 |
| 10-12 LLM响应 | ~300ms | ~300ms | 第二次模型调用 |
| 总耗时 | ~860ms | ~2.8s | - |
ls)可缓存结果sandboxSettings := &types.SandboxSettings{
// 启用沙箱隔离
Enabled: true,
// 沙箱启用时自动批准 bash 命令
AutoAllowBashIfSandboxed: true,
// 静态白名单,这些命令绕过沙箱限制
ExcludedCommands: []string{"git", "docker"},
// 允许模型请求绕过沙箱(需要权限审批)
AllowUnsandboxedCommands: true,
// 网络沙箱配置
Network: &types.NetworkSandboxSettings{
AllowLocalBinding: true, // 允许绑定本地端口
AllowUnixSockets: []string{"/var/run/docker.sock"}, // 允许的 Unix Socket
AllowAllUnixSockets: false, // 不允许所有 Socket
AllowedHosts: []string{"api.openai.com", "api.anthropic.com"},
BlockedHosts: []string{"malicious.com"},
HTTPProxyPort: 8080,
SOCKSProxyPort: 1080,
},
// 忽略特定违规
IgnoreViolations: &types.SandboxIgnoreViolations{
FilePatterns: []string{"/tmp/*", "*.log"},
NetworkPatterns: []string{"localhost:*", "127.0.0.1:*"},
},
// 启用较弱的嵌套沙箱(兼容性)
EnableWeakerNestedSandbox: false,
}
sandboxConfig := &types.SandboxConfig{
Kind: types.SandboxKindLocal,
WorkDir: "./workspace",
Settings: sandboxSettings,
PermissionMode: types.SandboxPermissionDefault,
}
| 字段 | 类型 | 说明 |
|---|---|---|
Enabled | bool | 是否启用沙箱隔离 |
AutoAllowBashIfSandboxed | bool | 沙箱启用时自动批准 bash 命令 |
ExcludedCommands | string | 绕过沙箱的命令白名单 |
AllowUnsandboxedCommands | bool | 允许模型请求绕过沙箱 |
Network | *NetworkSandboxSettings | 网络隔离配置 |
IgnoreViolations | *SandboxIgnoreViolations | 忽略的违规模式 |
type NetworkSandboxSettings struct {
AllowLocalBinding bool // 允许绑定本地端口(开发服务器)
AllowUnixSockets []string // 允许的 Unix Socket 路径
AllowAllUnixSockets bool // 允许所有 Unix Socket
HTTPProxyPort int // HTTP 代理端口
SOCKSProxyPort int // SOCKS 代理端口
AllowedHosts []string // 允许访问的主机
BlockedHosts []string // 禁止访问的主机
}
aster 支持四种权限模式,对应不同的安全级别:
| 模式 | 常量 | 说明 |
|---|---|---|
| 默认 | SandboxPermissionDefault | 标准权限检查流程 |
| 接受编辑 | SandboxPermissionAcceptEdits | 自动接受文件编辑操作 |
| 绕过权限 | SandboxPermissionBypass | 绕过所有权限检查(危险) |
| 规划模式 | SandboxPermissionPlan | 只记录不执行,用于预览 |
// 设置权限模式
sandboxConfig := &types.SandboxConfig{
Kind: types.SandboxKindLocal,
PermissionMode: types.SandboxPermissionAcceptEdits, // 自动接受编辑
}
CanUseTool 是 Claude Agent SDK 风格的自定义权限检查回调,允许应用层完全控制工具权限。
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 // 是否中断执行
}
canUseTool := func(
ctx context.Context,
toolName string,
input map[string]any,
opts *types.CanUseToolOptions,
) (*types.PermissionResult, error) {
// 1. 阻止访问敏感文件
if path, ok := input["path"].(string); ok {
if path == "/etc/passwd" || path == "/etc/shadow" {
return &types.PermissionResult{
Behavior: "deny",
Message: "Access to system files is not allowed",
}, nil
}
}
// 2. 自动批准读操作
if toolName == "Read" || toolName == "Glob" {
return &types.PermissionResult{Behavior: "allow"}, nil
}
// 3. 修改输入参数(添加超时)
if toolName == "Bash" {
input["timeout"] = 30000
return &types.PermissionResult{
Behavior: "allow",
UpdatedInput: input,
}, nil
}
// 4. 添加会话级规则
if toolName == "Write" {
return &types.PermissionResult{
Behavior: "allow",
UpdatedPermissions: []types.PermissionUpdate{
{
Type: "addRules",
Behavior: "allow",
Destination: "session",
Rules: []types.PermissionRule{
{ToolName: "Write", RuleContent: "auto-approved"},
},
},
},
}, nil
}
// 5. 返回 nil 让权限系统决策
return nil, nil
}
// 在 AgentConfig 中使用
agentConfig := &types.AgentConfig{
CanUseTool: canUseTool,
// ...
}
EnhancedInspector 是增强版权限检查器,整合了 CanUseTool 回调、沙箱配置和规则管理。
inspector := permission.NewEnhancedInspector(&permission.EnhancedInspectorConfig{
Mode: permission.ModeSmartApprove,
SandboxConfig: sandboxConfig,
CanUseTool: canUseTool,
PersistPath: ".aster/permissions.json",
AutoLoad: true,
})
graph TB
A[工具调用] --> B{权限模式检查}
B -->|bypass| C[直接允许]
B -->|plan| D[记录但不执行]
B -->|acceptEdits| E{是编辑工具?}
E -->|是| C
E -->|否| F[继续检查]
B -->|default| F
F --> G{CanUseTool 回调}
G -->|allow| C
G -->|deny| H[拒绝]
G -->|nil| I{沙箱配置检查}
I --> J{排除命令?}
J -->|是| C
J -->|否| K{绕过沙箱请求?}
K -->|是且不允许| H
K -->|是且允许| L[需要审批]
K -->|否| M{AutoAllowBash?}
M -->|是且是Bash| C
M -->|否| N{会话规则}
N -->|匹配| O[应用规则]
N -->|不匹配| P{持久化规则}
P -->|匹配| O
P -->|不匹配| Q{智能检查}
Q --> R{风险级别}
R -->|低| C
R -->|中| S{安全操作?}
S -->|是| C
S -->|否| L
R -->|高| L
type CheckResult struct {
Allowed bool // 是否允许
NeedsApproval bool // 是否需要审批
DecidedBy string // 决策来源
Message string // 消息
Interrupt bool // 是否中断
UpdatedInput map[string]any // 修改后的输入
ApprovalRequest *types.ControlPermissionRequiredEvent // 审批请求
}
// 记录违规
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()
模型可以在工具输入中请求绕过沙箱:
// 工具调用参数
{
"command": "sudo apt update",
"dangerouslyDisableSandbox": true
}
处理流程:
AllowUnsandboxedCommands 配置// 配置允许绕过请求
sandboxSettings := &types.SandboxSettings{
AllowUnsandboxedCommands: true, // 允许请求,但需要审批
}
type Sandbox interface {
// 执行命令
Execute(ctx context.Context, cmd *Command) (*Result, error)
// 文件操作
ReadFile(ctx context.Context, path string) ([]byte, error)
WriteFile(ctx context.Context, path string, content []byte) error
ListFiles(ctx context.Context, path string) ([]FileInfo, error)
// 生命周期
Start(ctx context.Context) error
Stop(ctx context.Context) error
Health(ctx context.Context) error
}
type Command struct {
Command string // 命令字符串
Args []string // 参数
Env map[string]string // 环境变量
WorkDir string // 工作目录
Timeout time.Duration // 超时时间
StdinData []byte // 标准输入
}
type Result struct {
Stdout string // 标准输出
Stderr string // 标准错误
ExitCode int // 退出码
Duration time.Duration // 执行时长
Error error // 错误(如有)
}
// Agent执行bash命令
ag.Chat(ctx, `
运行以下Go程序:
go run main.go
然后告诉我输出是什么
`)
沙箱中执行:
$ go run main.go
Hello, World!
// Agent操作文件
ag.Chat(ctx, `
1. 创建一个hello.txt文件
2. 写入"Hello aster"
3. 读取并验证内容
`)
沙箱操作:
$ echo "Hello aster" > /workspace/hello.txt
$ cat /workspace/hello.txt
Hello aster
// Agent安装依赖
ag.Chat(ctx, `
安装所需的Python包:
pip install requests beautifulsoup4
然后运行scraper.py
`)
// Agent运行测试
ag.Chat(ctx, `
运行所有单元测试:
go test ./...
如果有失败,分析原因
`)
Sandbox: &types.SandboxConfig{
Kind: types.SandboxKindAliyun,
Config: map[string]interface{}{
// CPU限制
"cpu_limit": 2.0, // 2个CPU核心
"cpu_request": 1.0, // 请求1个核心
// 内存限制
"memory_limit": 2048, // 2GB
"memory_request": 1024, // 请求1GB
// 磁盘限制
"disk_limit": 10240, // 10GB
// 超时限制
"timeout": 600, // 10分钟
"idle_timeout": 300, // 空闲5分钟自动销毁
},
}
Config: map[string]interface{}{
// 网络模式
"network_mode": "restricted", // none/restricted/full
// 允许的域名白名单
"allowed_domains": []string{
"api.github.com",
"registry.npmjs.org",
},
// 禁止的域名黑名单
"blocked_domains": []string{
"*.internal.com",
},
// 代理配置
"http_proxy": "http://proxy.example.com:8080",
}
Config: map[string]interface{}{
// 挂载卷
"volumes": []map[string]string{
{
"source": "/host/data",
"target": "/workspace/data",
"readonly": false,
},
},
// 状态持久化
"persistent_volume": "/mnt/agent-state",
"restore_on_start": true,
}
Config: map[string]interface{}{
// 自定义镜像
"image": "my-registry.com/agent-runtime:v1.0",
// 镜像拉取策略
"image_pull_policy": "IfNotPresent", // Always/IfNotPresent/Never
// 镜像凭据
"image_pull_secrets": []string{"my-registry-secret"},
}
Config: map[string]interface{}{
// 只读挂载
"readonly_paths": []string{
"/etc",
"/usr",
},
// 禁止访问
"blocked_paths": []string{
"/proc",
"/sys",
},
// 临时目录
"temp_dir": "/tmp/agent",
"auto_clean_temp": true,
}
Config: map[string]interface{}{
// 允许的命令
"allowed_commands": []string{
"ls", "cat", "echo",
"go", "python3", "node",
"git", "npm", "pip",
},
// 禁止的命令
"blocked_commands": []string{
"rm", "dd", "mkfs",
"sudo", "su",
},
}
Config: map[string]interface{}{
// 允许传递的环境变量
"env_whitelist": []string{
"PATH",
"HOME",
"LANG",
},
// 禁止的环境变量
"env_blacklist": []string{
"AWS_*",
"GITHUB_TOKEN",
},
}
// 订阅Monitor通道获取沙箱事件
monitorCh := ag.Subscribe([]types.AgentChannel{
types.ChannelMonitor,
}, nil)
for envelope := range monitorCh {
if e, ok := envelope.Event.(*types.MonitorSandboxExecutionEvent); ok {
log.Printf("沙箱执行: Command=%s Duration=%v ExitCode=%d",
e.Command, e.Duration, e.ExitCode)
}
}
type MonitorSandboxResourceEvent struct {
CPUUsage float64 // CPU使用率
MemoryUsage int64 // 内存使用(字节)
DiskUsage int64 // 磁盘使用(字节)
NetworkIn int64 // 网络入(字节)
NetworkOut int64 // 网络出(字节)
}
Config: map[string]interface{}{
// 资源告警阈值
"alert_cpu_threshold": 80.0, // CPU使用率超过80%
"alert_memory_threshold": 90.0, // 内存使用率超过90%
"alert_disk_threshold": 95.0, // 磁盘使用率超过95%
// 告警回调
"alert_webhook": "https://alerts.example.com/webhook",
}
// 开发环境
if os.Getenv("ENV") == "development" {
sandboxKind = types.SandboxKindLocal
}
// 测试环境
if os.Getenv("ENV") == "test" {
sandboxKind = types.SandboxKindMock
}
// 生产环境
if os.Getenv("ENV") == "production" {
sandboxKind = types.SandboxKindAliyun
}
Config: map[string]interface{}{
// 短任务
"timeout": 30, // 30秒
// 长任务(编译、测试)
"timeout": 600, // 10分钟
// 批量处理
"timeout": 3600, // 1小时
}
Config: map[string]interface{}{
"auto_clean": true,
"clean_on_stop": true,
"max_disk_usage": 5 * 1024 * 1024 * 1024, // 5GB
}
result, err := sandbox.Execute(ctx, cmd)
if err != nil {
// 检查错误类型
if errors.Is(err, context.DeadlineExceeded) {
log.Println("命令超时")
} else if errors.Is(err, sandbox.ErrResourceLimit) {
log.Println("资源限制")
} else {
log.Printf("执行失败: %v", err)
}
return err
}
// 检查退出码
if result.ExitCode != 0 {
log.Printf("命令失败: %s", result.Stderr)
}
// 复用沙箱实例
type SandboxPool struct {
pool chan sandbox.Sandbox
}
func (p *SandboxPool) Get() sandbox.Sandbox {
select {
case sb := <-p.pool:
return sb
default:
return sandbox.New()
}
}
func (p *SandboxPool) Put(sb sandbox.Sandbox) {
select {
case p.pool <- sb:
default:
sb.Stop(context.Background())
}
}
package main
import (
"context"
"fmt"
"github.com/astercloud/aster/pkg/agent"
"github.com/astercloud/aster/pkg/permission"
"github.com/astercloud/aster/pkg/types"
)
func main() {
ctx := context.Background()
// 1. 配置沙箱设置
sandboxSettings := &types.SandboxSettings{
Enabled: true,
AutoAllowBashIfSandboxed: true,
ExcludedCommands: []string{"git", "docker"},
AllowUnsandboxedCommands: true,
Network: &types.NetworkSandboxSettings{
AllowLocalBinding: true,
AllowedHosts: []string{"api.openai.com"},
},
IgnoreViolations: &types.SandboxIgnoreViolations{
FilePatterns: []string{"/tmp/*"},
},
}
sandboxConfig := &types.SandboxConfig{
Kind: types.SandboxKindLocal,
WorkDir: "./workspace",
Settings: sandboxSettings,
PermissionMode: types.SandboxPermissionDefault,
}
// 2. 自定义权限回调
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
}
// 3. 创建权限检查器
inspector := permission.NewEnhancedInspector(&permission.EnhancedInspectorConfig{
Mode: permission.ModeSmartApprove,
SandboxConfig: sandboxConfig,
CanUseTool: canUseTool,
})
// 4. 测试权限检查
call := &types.ToolCallSnapshot{
ID: "test-1",
Name: "Read",
Arguments: map[string]any{"path": "README.md"},
}
result, _ := inspector.Check(ctx, call)
fmt.Printf("Allowed: %v, DecidedBy: %s\n", result.Allowed, result.DecidedBy)
}
运行示例:
go run examples/sandbox-permission/main.go -api-key $ANTHROPIC_API_KEY