WASM 沙箱——Capability-Based 安全模型的工程实践

📑 目录

IronClaw 深度剖析(四):WASM 沙箱——Capability-Based 安全模型的工程实践

系列导读:在上一篇中,我们探讨了 IronClaw 的多租户资源调度系统。本篇将深入其核心安全基础设施——基于 WASM 的 Capability-Based 沙箱运行时,看看 IronClaw 如何在 Agent OS 场景下实现细粒度的权限控制与资源隔离。

当 AI Agent 开始执行用户安装的外部扩展时,一个根本性的安全问题浮出水面:如何在不信任扩展代码的前提下,安全地赋予它部分系统能力? 传统的容器方案虽然成熟,但启动慢、开销高,且权限粒度过于粗糙。IronClaw 选择了一条更具前瞻性的道路——基于 WebAssembly Component Model 和 Capability-Based 安全模型的混合沙箱架构。

这一选择的背后,是 Capsicum 和 CloudABI 等前沿安全思想的工程实践,也是对 Agent OS 场景下"高频率、低延迟、细权限"需求的精准回应。


一、WASM 沙箱架构概览

IronClaw 的 WASM 沙箱并非传统的 WebAssembly 运行时包装,而是一个围绕 wasmtime v43.0.2 构建的、深度集成的安全子系统。它采用 WASM Component Model + WIT(Wasm Interface Types)标准接口,取代了陈旧的 JSON ABI 指针传递模式,实现了类型安全、零拷贝的跨边界通信。

1.1 整体架构

graph TB
    subgraph "Upper Services"
        US["Reborn Turn/Loop Services"]
    end

    subgraph "Host Runtime Layer"
        HRT["HostRuntime Trait
(Facade)"] DHR["DefaultHostRuntime"] end subgraph "Capability Layer" CH["CapabilityHost"] AUTH["Authorizer"] APR["ApprovalResolver"] OBH["ObligationHandler"] end subgraph "Trust & Policy" TR["TrustPolicy"] HTP["HostTrustPolicy"] TC["TrustClass"] end subgraph "WASM Runtime" WTR["WitToolRuntime"] WTH["WitToolHost"] SD["StoreData"] WRL["WasmResourceLimiter"] end subgraph "Host API Contracts" HAPI["ironclaw_host_api"] EC["ExecutionContext"] CG["CapabilityGrant"] end US --> HRT HRT --> DHR DHR --> CH CH --> AUTH CH --> APR CH --> OBH AUTH --> TR TR --> HTP HTP --> TC CH --> WTR WTR --> SD SD --> WTH SD --> WRL HAPI --> EC HAPI --> CG EC --> CH CG --> CH

上图展示了从上层服务到 WASM 运行时的完整调用链。值得注意的是,CapabilityHost 是整个架构的核心协调者,它将授权、审批、义务、运行状态、调度和进程管理六个子系统编织成一个统一的调用流程。

1.2 关键安全配置

IronClaw 在 wasmtime 引擎层面实施了一系列安全强化配置,每一条都经过深思熟虑:

配置项设置值安全目的
wasm_component_modeltrue启用标准 Component Model
wasm_threadsfalse禁用线程,防止侧信道攻击
consume_fueltrue逐条指令资源计量
epoch_interruptiontrue支持基于 epoch 的超时中断
debug_infofalse禁用调试符号,防止信息泄露
pub fn new(config: WitToolRuntimeConfig) -> Result<Self, WasmError> {
    let mut wasmtime_config = Config::new();
    wasmtime_config.wasm_component_model(true);   // 启用 Component Model
    wasmtime_config.wasm_threads(false);           // 禁用线程(安全)
    wasmtime_config.consume_fuel(true);            // 启用燃料消耗限制
    wasmtime_config.epoch_interruption(true);      // 启用 epoch 中断
    wasmtime_config.debug_info(false);             // 禁用调试信息(安全)
    let engine = Engine::new(&wasmtime_config)
        .map_err(|error| WasmError::EngineCreationFailed(error.to_string()))?;
    spawn_epoch_ticker(engine.clone())?;  // 后台 ticker 强制执行超时
    Ok(Self { engine, config })
}

为什么禁用线程? 在多租户 Agent OS 中,WASM 线程可能通过共享内存实施 Spectre 类的侧信道攻击。IronClaw 选择禁用线程来消除这一攻击面,换取更高的安全保证。

1.3 资源限制策略

pub struct WitToolLimits {
    pub memory_bytes: u64,  // 默认: 10 MB
    pub fuel: u64,          // 默认: 500,000,000 条指令
    pub timeout: Duration,  // 默认: 60 秒
}

这组默认限制非常保守:10MB 内存足以处理大多数工具调用,5 亿条指令燃料覆盖绝大多数计算场景,60 秒超时防止无限循环。资源限制的精髓在于每个 WASM 指令都消耗燃料,实现了真正的 per-instruction 计量,而非粗糙的进程级限制。

1.4 防止 TOCTOU 攻击

Time-of-Check Time-of-Use(TOCTOU)是沙箱系统中的经典攻击面。IronClaw 在每个 host 函数调用的前后都检查执行 deadline:

fn tool_invoke(&mut self, alias: String, params_json: String) 
    -> Result<String, String> 
{
    // 调用前检查——防止超时后继续执行
    if let Some(error) = self.deadline_error() { return Err(error); }
    let result = self.host.tools.invoke(&alias, &params_json);
    // 调用后检查——防止 host 操作本身被利用
    if let Some(error) = self.deadline_error() { return Err(error); }
    result
}

这种"双重检查"模式确保即使 host 函数内部耗时过长,也不会给恶意扩展留下可乘之机。


二、Capability-Based 安全模型

IronClaw 安全架构的核心创新在于其 Capability-Based 安全模型,受 FreeBSD 的 Capsicum 和 CloudABI 启发。这一模型的根本原则是:默认拒绝一切,只有显式授予的能力才能被使用

2.1 fail-closed 哲学

在 Capability-Based 安全模型中,权限不是继承的,而是显式授予的。IronClaw 将这一理念贯彻到极致——每个 host 能力都有一个默认的"拒绝"实现:

#[derive(Debug, Default)]
pub struct DenyWasmHostHttp;
impl WasmHostHttp for DenyWasmHostHttp {
    fn request(&self, _request: WasmHttpRequest) -> Result<WasmHttpResponse, WasmHostError> {
        Err(WasmHostError::Unavailable(
            "WASM HTTP egress is not configured".to_string()
        ))
    }
}

HTTP 出网、密钥访问、工具调用、文件系统读写——所有能力默认关闭。只有当 ExecutionContext 中携带了对应的 CapabilityGrant 时,相关功能才会被解锁。

2.2 六子系统协调

CapabilityHost 是整个安全模型的大脑,它协调六个独立的子系统:

graph LR
    subgraph "CapabilityHost"
        CH_CENTER["CapabilityHost
中央协调器"] end subgraph "六大子系统" AUTH["① 授权子系统
TrustAwareAuthorizer"] APR["② 审批子系统
ApprovalRequestStore"] RS["③ 运行状态
RunStateStore"] OB["④ 义务系统
CapabilityObligationHandler"] PM["⑤ 进程管理
ProcessManager"] CL["⑥ 租约管理
CapabilityLeaseStore"] end CH_CENTER --> AUTH CH_CENTER --> APR CH_CENTER --> RS CH_CENTER --> OB CH_CENTER --> PM CH_CENTER --> CL AUTH -. "Decision::Allow" .-> CH_CENTER AUTH -. "Decision::Deny" .-> CH_CENTER AUTH -. "Decision::RequireApproval" .-> APR APR -. "CapabilityLease" .-> CL CL -. "claim/consume" .-> CH_CENTER OB -. "prepare/complete" .-> CH_CENTER PM -. "spawn/monitor" .-> CH_CENTER RS -. "Blocked/Running/Completed" .-> CH_CENTER

这六个子系统各司其职:

子系统职责关键交互
授权子系统判断调用者是否有权限返回 Decision
审批子系统管理人机交互审批流程持久化审批请求
运行状态跟踪调用生命周期状态机转换
义务系统执行授权的前置/后置条件preparecomplete
进程管理管理派生进程异步任务
租约管理管理审批后的临时授权防重放

2.3 义务系统:Decision::Allow 不只是"允许"

IronClaw 的义务系统(Obligation System)是其安全模型中最精妙的设计之一。Decision::Allow 并非简单的"放行",而是携带了一系列必须被满足的前置和后置条件

pub enum Decision {
    Allow { obligations: Vec<Obligation> },  // 授予,但附带义务
    Deny { reason: DenyReason },              // 拒绝,附带原因
    RequireApproval { request: ApprovalRequest }, // 需要人工审批
}

pub enum Obligation {
    AuditAfter,                           // 要求审计日志
    RedactOutput,                         // 脱敏输出
    ApplyMountPolicy { view: MountView }, // 应用文件系统挂载限制
    ApplyNetworkPolicy { policy: NetworkPolicy }, // 限制网络访问
    EnforceResourceCeiling { ceiling: ResourceCeiling }, // 资源上限
    EnforceOutputLimit { max_bytes: u64 }, // 输出大小限制
    InjectCredential { ... },             // 注入凭证
}

当授权子系统返回 Allow 时,义务处理器的 prepare() 方法首先执行所有前置义务(如挂载文件系统视图、注入凭证),然后调用分发器执行实际能力,最后 complete_dispatch() 执行后置义务(如审计日志、输出脱敏)。这种设计将授权与执行彻底分离,使得同一份授权可以在不同场景下附加不同的安全策略。

2.4 Capability 授权完整流程

下面这张流程图展示了从调用请求到执行完成的完整 Capability 授权流程:

flowchart TD
    START(["调用请求"]) --> VALIDATE["① 验证 ExecutionContext
一致性检查"] VALIDATE --> FINGERPRINT["② 计算调用指纹
Integrity Check"] FINGERPRINT --> RECORD["③ 记录运行开始
RunState::Running"] RECORD --> LOOKUP["④ 查询能力描述符
ExtensionRegistry"] LOOKUP --> AUTH["⑤ 授权检查
authorizer.authorize_dispatch_with_trust()"] AUTH -->|"Decision::Deny"| DENY["返回拒绝错误
附带 DenyReason"] DENY --> END_DENY([结束]) AUTH -->|"Decision::RequireApproval"| SAVE["保存审批请求
ApprovalRequestStore"] SAVE --> BLOCK["运行状态 → BlockedApproval"] BLOCK --> RETURN["返回 ApprovalRequired
等待人工审批"] RETURN --> END_APPROVAL([挂起]) AUTH -->|"Decision::Allow
+ Obligations"| PREPARE["⑥ 义务准备
prepare() 前置义务"] PREPARE --> DISPATCH["⑦ 能力分发
dispatch_json()"] DISPATCH --> WASM{"运行时类型"} WASM -->|"RuntimeKind::Wasm"| WEXEC["WitToolRuntime::execute()
wasmtime Store + StoreData"] WASM -->|"RuntimeKind::Docker"| DEXEC["Docker Runtime"] WASM -->|"RuntimeKind::Mcp"| MEXEC["MCP Runtime"] WEXEC --> DEADLINE["每次 host 调用前后
检查 deadline"] DEXEC --> COMPLETE MEXEC --> COMPLETE DEADLINE --> COMPLETE["⑧ 义务完成
complete_dispatch() 后置义务"] COMPLETE --> DONE["运行状态 → Completed
返回 RuntimeCapabilityOutcome"] DONE --> END_OK([结束]) style START fill:#e1f5fe style END_DENY fill:#ffcdd2 style END_APPROVAL fill:#fff9c4 style END_OK fill:#c8e6c9 style AUTH fill:#f3e5f5 style PREPARE fill:#e8f5e9 style COMPLETE fill:#e8f5e9

这个流程涵盖了所有可能的分支路径:正常允许、直接拒绝、需要人工审批。每种状态都有明确的状态转换和对应的处理方式。


三、Host API 设计:ironclaw_host_api

ironclaw_host_api 是一个纯合约 crate,不包含任何运行时行为,只承载权限相关的类型定义。它是整个 IronClaw 系统服务的"安全词汇表"。

3.1 ExecutionContext:完整的权限信封

ExecutionContext 是每次能力调用的安全上下文,包含了调用者身份、能力授权、文件系统视图和资源范围:

pub struct ExecutionContext {
    pub invocation_id: InvocationId,
    pub correlation_id: CorrelationId,
    pub process_id: Option<ProcessId>,
    pub parent_process_id: Option<ProcessId>,
    
    // 身份标识
    pub tenant_id: TenantId,
    pub user_id: UserId,
    pub agent_id: Option<AgentId>,
    pub project_id: Option<ProjectId>,
    pub mission_id: Option<MissionId>,
    pub thread_id: Option<ThreadId>,
    
    // 运行时
    pub extension_id: ExtensionId,
    pub runtime: RuntimeKind,       // Wasm / Docker / Mcp
    pub trust: TrustClass,          // 信任等级
    
    // 授权
    pub grants: CapabilitySet,      // 显式能力授予
    pub mounts: MountView,          // 文件系统可见性
    pub resource_scope: ResourceScope, // 资源边界
}

Validation 确保一致性:resource_scope.invocation_id 必须匹配 execution_context.invocation_id,所有 scoped 字段必须一致。这种严格的校验防止了权限上下文的篡改。

3.2 CapabilityGrant:显式、限界、限时

pub struct CapabilityGrant {
    pub id: CapabilityGrantId,
    pub capability: CapabilityId,
    pub principal: Principal,         // 谁被授予
    pub effect: Vec<EffectKind>,      // 允许的效果
    pub constraints: GrantConstraints, // 使用约束
    pub scope: GrantScope,            // 作用范围
    pub issued_at: Timestamp,
    pub expires_at: Option<Timestamp>, // 过期时间
}

CapabilityGrant 有三个关键属性:显式(必须通过授权子系统签发)、限界(只能作用于指定的 scope)、限时(有过期时间)。这三个属性共同确保了权限的最小化原则。


四、Host Runtime:ironclaw_host_runtime

ironclaw_host_runtime 是上层服务的窄边界,采用门面模式(Facade Pattern)提供稳定契约。

4.1 门面设计

#[async_trait]
pub trait HostRuntime: Send + Sync {
    async fn invoke_capability(&self, request: RuntimeCapabilityRequest)
        -> Result<RuntimeCapabilityOutcome, HostRuntimeError>;

    async fn resume_capability(&self, request: RuntimeCapabilityResumeRequest)
        -> Result<RuntimeCapabilityOutcome, HostRuntimeError>;

    async fn visible_capabilities(&self, request: VisibleCapabilityRequest)
        -> Result<VisibleCapabilitySurface, HostRuntimeError>;

    async fn cancel_work(&self, request: CancelRuntimeWorkRequest)
        -> Result<CancelRuntimeWorkOutcome, HostRuntimeError>;

    async fn runtime_status(&self, request: RuntimeStatusRequest)
        -> Result<HostRuntimeStatus, HostRuntimeError>;

    async fn health(&self) -> Result<HostRuntimeHealth, HostRuntimeError>;
}

生产环境使用 DefaultHostRuntime,内部包装 CapabilityHost,但上层服务对此无感知。这种解耦允许未来替换运行时实现而不影响上层代码。

4.2 支持三种运行时

IronClaw 并非只支持 WASM,而是通过 RuntimeKind 枚举支持三种运行时:

  • RuntimeKind::Wasm — 轻量级 WASM 沙箱
  • RuntimeKind::Docker — 完整容器隔离
  • RuntimeKind::Mcp — Model Context Protocol 运行时

三种运行时共享同一套授权、审批和信任策略,CapabilityHost 根据能力描述符自动路由到对应运行时。

4.3 HTTP 出站安全:多层泄漏检测

HostHttpEgressService 实现了四层凭证泄漏防护:

fn validate_runtime_request(request: &RuntimeHttpEgressRequest) -> Result<(), ...> {
    // 第 1 层:阻止敏感 Header
    if let Some((name, _)) = request.headers.iter()
        .find(|(name, _)| is_sensitive_runtime_request_header(name)) {
        return Err(...);
    }

    // 第 2 层:检测手动嵌入凭证
    if runtime_request_contains_manual_credentials(request) {
        return Err(...);
    }

    // 第 3 层:扫描 URL 中的凭证泄露
    detector.scan_http_request(&request.url, &request.headers, Some(&request.body))?;
    scan_decoded_url_for_leaks(&detector, &request.url)?;

    // 第 4 层:响应编辑——自动脱敏
    // 在生产模式下,没有 NetworkObligationPolicyStore 则直接失败(fail-closed)
    Ok(())
}

这种** fail-closed** 设计确保了凭证安全不是可选项,而是强制要求。


五、信任系统:ironclaw_trust

信任系统是 IronClaw 安全模型的基石,它定义了"谁可以被信任到什么程度"。

5.1 三大不变量

IronClaw 信任系统基于三个不可违反的核心不变量:

  1. 有效信任由 host 策略唯一决定FirstPartySystem 信任等级只能在 ironclaw_trust crate 内部构造,外部代码无法伪造高信任等级。

  2. 信任是天花板,而非授权TrustDecision 返回的是 AuthorityCeiling(最多可以授予什么),而非实际授予的权限。实际授权还需通过 CapabilityGrant。

  3. 信任变更使活跃授权失效TrustChange 在信任策略变更后被同步发布,确保后续分派只能观察到新的信任决策。

5.2 六级信任等级

graph TD
    subgraph "信任等级金字塔"
        direction TB
        SYS["System
Host 构建的系统组件
最高权限"] FP["FirstParty
Host 审核的捆绑组件"] SIG["Signed
可信签名者密码学签名"] UT["UserTrusted
用户显式信任"] TP["ThirdParty
未经审核的第三方"] SB["Sandbox
沙箱——最小权限
未知来源默认值"] end SYS --> FP FP --> SIG SIG --> UT UT --> TP TP --> SB
pub enum EffectiveTrustClass {
    System,        // Host 构建的系统组件(最高)
    FirstParty,    // Host 审核的捆绑组件
    Signed,        // 可信签名者密码学签名
    UserTrusted,   // 用户显式信任
    ThirdParty,    // 未经审核的第三方
    Sandbox,       // 沙箱——最小权限(未知来源默认)
}

这六个等级构成了一个严格的偏序关系,每一级都严格低于上一级。所有未匹配的来源都默认落入 Sandbox 等级——这是 fail-closed 原则在信任系统中的体现。

5.3 原子变更模式:mutate_with

信任策略的变更是安全敏感操作。IronClaw 使用 mutate_with 模式确保原子性:

pub fn mutate_with<F, R>(
    &self,
    bus: &InvalidationBus,
    affected_identity: PackageIdentity,
    requested_authority: BTreeSet<CapabilityId>,
    requested_trust: RequestedTrustClass,
    f: F,
) -> Result<R, TrustError>
where
    F: FnOnce(&SourceMutators<'_>) -> Result<R, TrustError>,
{
    let _gate = self.mutation_gate.write()?;       // ① 获取写锁
    let prev = self.evaluate_unlocked(&probe)?;    // ② 记录变更前状态
    let result = f(&mutators)?;                    // ③ 执行用户闭包
    mutators.commit()?;                            // ④ 提交变更
    let curr = self.evaluate_unlocked(&probe)?;    // ⑤ 评估变更后状态
    if let Some(change) = TrustChange::new(identity, &prev, &curr) {
        bus.publish(change);                       // ⑥ 发布失效通知
    }
    Ok(result)
}

这个模式的精妙之处在于:如果闭包执行失败,所有变更都会被丢弃;只有当闭包成功且变更提交后,才会比较前后状态并发布失效通知。这保证了 AC #6(Authority Ceiling Invariant)是一个编译期保证——信任变更无法绕过失效通知。

5.4 默认决策:Sandbox

fn default_decision(_input: &TrustPolicyInput, evaluated_at: Timestamp) -> TrustDecision {
    TrustDecision {
        effective_trust: EffectiveTrustClass::sandbox(),  // 沙箱!
        authority_ceiling: AuthorityCeiling::empty(),      // 无任何权限
        provenance: TrustProvenance::Default,
        evaluated_at,
    }
}

所有未匹配的来源都落入 Sandbox。没有例外,没有后门。


六、审批系统:ironclaw_approvals

审批系统实现了"审批与执行分离"的设计哲学。它不提示用户,不执行能力,不调度运行时工作——它只负责一件事:将人工审批决议转化为临时、受限的授权凭证

6.1 CapabilityLease:临时授权的精髓

当授权子系统返回 Decision::RequireApproval 时,CapabilityHost 将审批请求持久化,并将运行状态设置为 BlockedApproval。人工审批后,ApprovalResolver 创建一个 CapabilityLease

pub struct CapabilityLease {
    pub id: CapabilityLeaseId,
    pub grant: CapabilityGrant,            // 实际授予
    pub fingerprint: InvocationFingerprint, // 绑定到特定调用指纹
    pub approved_at: Timestamp,
    pub expires_at: Timestamp,             // 限时
}

CapabilityLease 有四个关键安全属性:

  • 绑定调用指纹:只能用于特定的调用,防止参数替换攻击
  • 限时:有过期时间,防止长期有效的授权
  • 单次使用:成功使用后即被消耗,防止重放攻击
  • 先申领后使用:必须先 claim 再使用,防止并发竞争
// 防竞争模式
let claimed_lease = capability_leases
    .claim(&scope, lease.grant.id, &invocation_fingerprint)
    .await?;
// ... 使用 lease ...
capability_leases.consume(&scope, claimed_lease.grant.id).await?;

claim 操作是原子的——如果两个调用者同时尝试恢复同一个审批请求,只有一个能成功申领到 lease,另一个将收到错误。


七、与 Docker 沙箱的对比

IronClaw 并非"WASM 取代 Docker"的二元选择,而是根据场景选择最合适的隔离级别。

维度IronClaw WASM 沙箱Docker 沙箱
启动时间毫秒级(WASM 实例化)秒级(容器启动)
内存开销低(共享运行时)高(独立内核、文件系统)
资源限制Fuel 逐指令计量cgroups 粗粒度限制
能力模型显式能力授予全有或全无的容器权限
文件系统挂载视图(显式可见性)完整容器文件系统
网络分级策略,显式出网容器级网络命名空间
隔离级别软件级(wasmtime)OS 级(namespaces, cgroups)
攻击面小(WASM 沙箱 + host API)大(Linux 内核、容器运行时)
凭证安全注入 + 泄漏检测手动管理
审计追踪内置义务系统需要外部日志
扩展性高(轻量实例)较低(重量级容器)
兼容性WIT/Component ModelOCI/Docker 生态

WASM 沙箱适合:短期工具调用、用户安装的扩展、细粒度权限控制、高密度多租户、凭证敏感操作。

Docker 沙箱适合:长期运行服务、复杂多进程应用、完整 Linux 兼容需求、重计算负载、GPU 访问需求。

IronClaw 的混合架构让开发者可以根据具体需求选择隔离级别——轻量工具用 WASM,重型服务用 Docker,外部工具用 MCP——所有运行时共享同一套安全基础设施。


八、总结

IronClaw 的 WASM 沙箱架构代表了 Agent OS 安全模型的工程巅峰。它将多个前沿安全机制融合为一个统一系统:

  1. Capability-Based 安全模型:显式、限界、限时的授权,fail-closed 默认
  2. 义务系统:授权决策携带前置/后置条件,实现授权与执行的分离
  3. 六级信任等级:严格的偏序关系,未知来源默认沙箱
  4. 原子变更模式mutate_with 保证信任变更的 ACID 语义
  5. 审批与执行分离:CapabilityLease 防重放、防竞争、限时单次使用
  6. 多层凭证泄漏防护:从 Header 验证到响应编辑的四层防御

这些机制不是简单的功能堆砌,而是围绕一个核心原则精心设计的:安全不是事后补救,而是系统的基础设计原则,并在编译期强制执行

在 AI Agent 即将大规模执行外部代码的时代,IronClaw 的安全架构为我们展示了一个值得追求的方向——既能充分释放 AI 的能力,又能将风险控制在最小范围。

下篇预告:《IronClaw 深度剖析(五)》:工具生态系统——从注册发现到动态加载的完整链路