> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/nearai/ironclaw/llms.txt
> Use this file to discover all available pages before exploring further.

# WASM Sandbox Security

> WebAssembly-based isolation with capability-based permissions and resource limits

IronClaw runs all untrusted tools in **WebAssembly (WASM) sandboxes** powered by Wasmtime. This provides strong isolation without the overhead of Docker containers or VMs.

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                    WASM Tool Execution                          │
│                                                                  │
│   WASM Tool ──► Host Function ──► Allowlist ──► Credential     │
│   (untrusted)   (boundary)        Validator     Injector        │
│                                                      │           │
│                                                      ▼           │
│                                                  Execute         │
│                                                  Request         │
│                                                      │           │
│                                                      ▼           │
│                               ◄──── Leak Detector ◄─ Response   │
│                          (sanitized, no secrets)                │
└─────────────────────────────────────────────────────────────────┘
```

## Security Guarantees

### Memory Isolation

WASM tools run in **linear memory** completely isolated from the host:

* **Default limit**: 10 MB per tool
* **No heap access**: Cannot read host memory
* **No pointers**: Cannot access arbitrary addresses
* **Bounds checking**: All memory accesses validated by WASM runtime

Configured via `ResourceLimiter`:

```rust:src/tools/wasm/limits.rs theme={null}
pub const DEFAULT_MEMORY_LIMIT: usize = 10 * 1024 * 1024; // 10 MB

impl ResourceLimiter for WasmResourceLimiter {
    fn memory_growing(
        &mut self,
        current: usize,
        desired: usize,
        _maximum: Option<usize>,
    ) -> Result<bool> {
        if desired > self.max_memory {
            return Ok(false); // Deny growth beyond limit
        }
        Ok(true)
    }
}
```

### CPU Metering

WASM execution is **fuel-metered** to prevent infinite loops and CPU exhaustion:

* **Fuel**: Virtual "gas" consumed per WASM instruction
* **Default limit**: Configurable per tool (typically millions of instructions)
* **Epoch interruption**: Periodic checks for timeouts
* **Tokio timeout**: Hard wall-clock limit (default 30s)

Example from [src/tools/wasm/limits.rs:18-28](~/workspace/source/src/tools/wasm/limits.rs#L18-L28):

```rust theme={null}
// Default fuel limit (approx 10M instructions)
pub const DEFAULT_FUEL_LIMIT: u64 = 10_000_000;

// Configure in engine
config.consume_fuel(true);
store.add_fuel(fuel_config.initial_fuel)?;

// Check remaining fuel during execution
let remaining = store.get_fuel()?;
```

### No System Access

WASM tools have **zero system access** by default:

* ❌ No filesystem (no WASI FS imports)
* ❌ No raw sockets
* ❌ No subprocess spawning
* ❌ No environment variable access
* ❌ No system clock (host provides `now_millis()`)

All capabilities must be **explicitly granted** via host functions.

### Fresh Instances

Each tool execution creates a **fresh WASM instance**:

* **Compile once**: WASM binary validated and compiled at load time
* **Instantiate per execution**: New memory, new store, new state
* **No state reuse**: Previous execution's memory is discarded
* **Side-channel prevention**: No shared state between executions

From [src/tools/wasm/runtime.rs](~/workspace/source/src/tools/wasm/runtime.rs):

```rust theme={null}
// Compile once during registration
let module = engine.compile(wasm_bytes)?;
let prepared = PreparedModule { module, ... };

// Fresh instance per execution
let instance = linker.instantiate(&mut store, &prepared.module)?;
```

## Capability System

All WASM tools start with **zero capabilities**. Each must be explicitly granted:

### Available Capabilities

| Capability | Purpose | Risk Level |
| - | - | - |
| **HTTP** | Make HTTP requests to allowlisted endpoints | Medium-High |
| **Secrets** | Check if secrets exist (not read values) | Low |
| **Workspace** | Read files from workspace (read-only) | Low-Medium |
| **ToolInvoke** | Call other tools via aliases | Medium |

### Capability Configuration

Capabilities are defined in `*.capabilities.json` files:

```json theme={null}
{
  "http": {
    "allowlist": [
      {
        "host": "api.openai.com",
        "path_prefix": "/v1/",
        "methods": ["POST"]
      }
    ],
    "rate_limit": {
      "requests_per_minute": 60,
      "requests_per_hour": 1000
    }
  },
  "secrets": {
    "allowed_names": ["openai_api_key"]
  },
  "workspace_read": {
    "allowed_prefixes": ["context/", "daily/"]
  }
}
```

### HTTP Capability

The most sensitive capability. Enforces:

1. **Host allowlisting**: Only approved domains
2. **Path restrictions**: Specific API endpoints only
3. **Method controls**: GET/POST/etc. per endpoint
4. **Rate limiting**: Requests per minute/hour
5. **Size limits**: Max request/response body sizes
6. **HTTPS enforcement**: No plaintext HTTP
7. **Credential injection**: Secrets added by host, not WASM

From [src/tools/wasm/capabilities.rs:102-169](~/workspace/source/src/tools/wasm/capabilities.rs#L102-L169):

```rust theme={null}
pub struct HttpCapability {
    pub allowlist: Vec<EndpointPattern>,
    pub credentials: HashMap<String, CredentialMapping>,
    pub rate_limit: RateLimitConfig,
    pub max_request_bytes: usize,   // Default: 1 MB
    pub max_response_bytes: usize,  // Default: 10 MB
    pub timeout: Duration,          // Default: 30s
}
```

### Secrets Capability

Tools can check **existence** but never read plaintext:

```rust theme={null}
// WASM can call
let exists = host.secret_exists("openai_api_key"); // true/false

// WASM cannot call (not exposed)
let value = host.get_secret("openai_api_key"); // ❌ Not available
```

Credentials are injected at the host boundary during HTTP requests. See [Credential Management](/security/credentials).

### Workspace Capability

Read-only access to workspace files with prefix restrictions:

```rust theme={null}
// Allowed (if "context/" prefix is granted)
let content = workspace_read("context/notes.md");

// Blocked (prefix not in allowed list)
let content = workspace_read("secrets/api_keys.txt"); // ❌ Access denied

// Blocked (path traversal)
let content = workspace_read("../../../etc/passwd"); // ❌ Validation fails
```

Path validation from [src/channels/wasm/capabilities.rs:136-157](~/workspace/source/src/channels/wasm/capabilities.rs#L136-L157):

```rust theme={null}
fn validate_workspace_path(path: &str) -> Result<String, String> {
    // Block absolute paths
    if path.starts_with('/') {
        return Err("Absolute paths not allowed");
    }
    
    // Block path traversal
    if path.contains("..") {
        return Err("Parent directory references not allowed");
    }
    
    // Block null bytes
    if path.contains('\0') {
        return Err("Null bytes not allowed");
    }
    
    Ok(path)
}
```

### ToolInvoke Capability

Indirect tool access via **aliases** (prevents direct tool enumeration):

```json theme={null}
{
  "tool_invoke": {
    "aliases": {
      "search": "web_search_tool",
      "summarize": "summarization_tool"
    }
  }
}
```

WASM only knows aliases, not real tool names.

## Security Constraints

| Threat | Mitigation | Implementation |
| - | - | - |
| **CPU exhaustion** | Fuel metering | `store.add_fuel()`, epoch interrupts |
| **Memory exhaustion** | ResourceLimiter | 10MB default, growth denied |
| **Infinite loops** | Timeout + epochs | `tokio::time::timeout()` |
| **Filesystem access** | No WASI FS | Only `workspace_read` host function |
| **Network access** | Allowlist only | `AllowlistValidator` |
| **Credential exposure** | Injection boundary | Secrets never enter WASM memory |
| **Secret exfiltration** | Leak detector | Scan requests/responses |
| **Log spam** | Entry limits | Max 1000 entries, 4KB per message |
| **Path traversal** | Path validation | No `..`, no `/` prefix |
| **Trap recovery** | Discard instance | Never reuse after trap |
| **Side channels** | Fresh instance | New memory per execution |
| **Rate abuse** | Per-tool limits | RateLimiter per capability |
| **Binary tampering** | BLAKE3 hashing | Hash verification on load |

## Host Functions (V2 API)

WASM tools interact with the host via these functions:

### Logging

```rust theme={null}
fn log(level: i32, message_ptr: i32, message_len: i32)
```

### Time

```rust theme={null}
fn now_millis() -> i64
```

### Workspace Read

```rust theme={null}
fn workspace_read(path_ptr: i32, path_len: i32) -> i32  // Returns result handle
```

### HTTP Request

```rust theme={null}
fn http_request(
    method_ptr: i32, method_len: i32,
    url_ptr: i32, url_len: i32,
    headers_ptr: i32, headers_len: i32,
    body_ptr: i32, body_len: i32
) -> i32  // Returns result handle
```

### Secret Exists

```rust theme={null}
fn secret_exists(name_ptr: i32, name_len: i32) -> i32  // 1 = exists, 0 = not found
```

### Tool Invoke

```rust theme={null}
fn tool_invoke(
    alias_ptr: i32, alias_len: i32,
    params_ptr: i32, params_len: i32
) -> i32  // Returns result handle
```

All string parameters are UTF-8 encoded. Buffers are bounds-checked.

## Failure Modes

### WASM Trap

If WASM code traps (invalid memory access, division by zero, etc.):

1. Execution stops immediately
2. Instance is discarded (never reused)
3. Error returned to caller
4. No host state corruption

Example trap handling:

```rust theme={null}
match instance.execute(&mut store, params) {
    Ok(result) => result,
    Err(e) if e.is_wasm_trap() => {
        tracing::error!(tool = tool_name, "WASM trap: {}", e);
        return Err(WasmError::Trap { ... });
    }
}
```

### Fuel Exhaustion

```rust theme={null}
WasmError::OutOfFuel {
    tool: "expensive_tool",
    fuel_used: 10000000,
}
```

### Memory Limit Exceeded

```rust theme={null}
WasmError::MemoryLimitExceeded {
    tool: "greedy_tool",
    attempted: 15_000_000,
    limit: 10_000_000,
}
```

### Capability Denied

```rust theme={null}
WasmError::CapabilityDenied {
    tool: "sneaky_tool",
    capability: "http",
    reason: "Host not in allowlist: evil.com",
}
```

## Best Practices

### For Tool Authors

1. **Request minimal capabilities**: Only what you need
2. **Use tight allowlists**: Specific hosts and paths
3. **Handle errors gracefully**: Capability denials, rate limits
4. **Avoid secrets in logs**: Use `secret_exists()` checks
5. **Respect fuel limits**: Optimize expensive operations

### For System Administrators

1. **Audit capabilities.json**: Review all granted permissions
2. **Monitor logs**: Watch for denied requests (potential attacks)
3. **Rotate secrets**: Use `expires_at` for temporary credentials
4. **Set conservative limits**: Start with low fuel/memory, increase if needed
5. **Keep allowlists tight**: Only domains you trust

## Source Code References

* **WASM runtime**: [src/tools/wasm/runtime.rs](~/workspace/source/src/tools/wasm/runtime.rs)
* **Capabilities**: [src/tools/wasm/capabilities.rs:1-200](~/workspace/source/src/tools/wasm/capabilities.rs#L1-L200)
* **Resource limits**: [src/tools/wasm/limits.rs](~/workspace/source/src/tools/wasm/limits.rs)
* **Host functions**: [src/tools/wasm/host.rs](~/workspace/source/src/tools/wasm/host.rs)
* **Allowlist validation**: [src/tools/wasm/allowlist.rs:74-164](~/workspace/source/src/tools/wasm/allowlist.rs#L74-L164)

## See Also

* [Network Security](/security/network-security) - HTTP allowlisting and rate limiting
* [Credential Management](/security/credentials) - Secret injection at boundary
* [Security Overview](/security/overview) - Complete security architecture
