> ## 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 Tool System

> Build sandboxed, capability-controlled tools with WebAssembly

IronClaw's WASM tool system allows you to build custom tools in Rust that compile to WebAssembly and run in a sandboxed environment with strict security controls.

**Location**: `src/tools/wasm/`

## Architecture

```text theme={null}
┌─────────────────────────────────────────────────────────────────────────────┐
│                              WASM Tool Execution                             │
│                                                                              │
│   WASM Tool ──▶ Host Function ──▶ Allowlist ──▶ Credential ──▶ Execute     │
│   (untrusted)   (boundary)        Validator     Injector       Request      │
│                                                                    │        │
│                                                                    ▼        │
│                              ◀────── Leak Detector ◀────── Response        │
│                          (sanitized, no secrets)                            │
└─────────────────────────────────────────────────────────────────────────────┘
```

**Key principles**:

* **Compile once, instantiate fresh**: Tools are validated and compiled at registration time. Each execution creates a fresh instance (NEAR pattern).
* **Fuel metering**: CPU usage is limited via Wasmtime's fuel system.
* **Memory limits**: Memory growth is bounded via ResourceLimiter (default 10MB).
* **Capability-based security**: Features are opt-in via Capabilities.
* **Credential isolation**: Tools never see raw credentials, only placeholder names.

**Location**: `src/tools/wasm/mod.rs:1-75`

## Security Constraints

| Threat | Mitigation |
| - | - |
| CPU exhaustion | Fuel metering |
| Memory exhaustion | ResourceLimiter, 10MB default |
| Infinite loops | Epoch interruption + tokio timeout |
| Filesystem access | No WASI FS, only host `workspace_read` |
| Network access | Allowlisted endpoints only |
| Credential exposure | Injection at host boundary only |
| Secret exfiltration | Leak detector scans all outputs |
| Log spam | Max 1000 entries, 4KB per message |
| Path traversal | Validate paths (no `..`, no `/` prefix) |
| Trap recovery | Discard instance, never reuse |
| WASM tampering | BLAKE3 hash verification on load |

**Location**: `src/tools/wasm/mod.rs:32-50`

## WIT Interface

WASM tools implement the `sandboxed-tool` world defined in `wit/tool.wit`:

```wit theme={null}
package near:agent@0.2.0;

world sandboxed-tool {
    import host;
    export tool;
}

interface host {
    enum log-level { trace, debug, info, warn, error }
    
    // Logging
    log: func(level: log-level, message: string);
    
    // Time access
    now-millis: func() -> u64;
    
    // Workspace read access
    workspace-read: func(path: string) -> option<string>;
    
    // HTTP requests (with allowlist validation)
    http-request: func(
        method: string,
        url: string,
        headers-json: string,
        body: option<list<u8>>,
        timeout-ms: option<u32>,
    ) -> result<http-response, string>;
    
    record http-response {
        status: u16,
        headers-json: string,
        body: list<u8>,
    }
    
    // Secret existence check (not the value)
    secret-exists: func(name: string) -> bool;
    
    // Invoke other tools (with permission)
    tool-invoke: func(
        name: string,
        params-json: string,
    ) -> result<string, string>;
}

interface tool {
    // Tool metadata
    name: func() -> string;
    description: func() -> string;
    parameters-schema: func() -> string;
    
    // Main execution function
    execute: func(params-json: string) -> result<string, string>;
}
```

**Location**: `wit/tool.wit`

## Capabilities System

Capabilities define what a WASM tool is allowed to do. They are declared in a `<tool-name>.capabilities.json` file.

**Location**: `src/tools/wasm/capabilities.rs`

### HTTP Capability

```rust theme={null}
pub struct HttpCapability {
    pub allowlist: Vec<EndpointPattern>,
    pub rate_limit: Option<RateLimitConfig>,
    pub timeout_secs: Option<u64>,
    pub credentials: HashMap<String, CredentialMapping>,
}

pub struct EndpointPattern {
    pub host: String,
    pub path_prefix: Option<String>,
    pub methods: Vec<String>,
}
```

**Example** (`github-tool.capabilities.json`):

```json theme={null}
{
  "capabilities": {
    "http": {
      "allowlist": [
        {
          "host": "api.github.com",
          "path_prefix": "/",
          "methods": ["GET", "POST"]
        }
      ],
      "credentials": {
        "github_token": {
          "secret_name": "github_token",
          "location": {"type": "bearer"},
          "host_patterns": ["api.github.com"]
        }
      },
      "rate_limit": {
        "requests_per_minute": 60,
        "requests_per_hour": 3600
      }
    },
    "secrets": {
      "allowed_names": ["github_token", "github_*"]
    }
  }
}
```

**Location**: `src/tools/wasm/capabilities_schema.rs:28-80`

### Workspace Capability

```rust theme={null}
pub struct WorkspaceCapability {
    pub allowed_paths: Vec<String>,
    pub read_only: bool,
}
```

Example:

```json theme={null}
{
  "workspace": {
    "allowed_paths": ["docs/", "*.md"],
    "read_only": true
  }
}
```

### Secrets Capability

```rust theme={null}
pub struct SecretsCapability {
    pub allowed_names: Vec<String>,
}
```

Example:

```json theme={null}
{
  "secrets": {
    "allowed_names": ["github_token", "github_*"]
  }
}
```

### Tool Invoke Capability

```rust theme={null}
pub struct ToolInvokeCapability {
    pub allowed_tools: Vec<String>,
}
```

Example:

```json theme={null}
{
  "tool_invoke": {
    "allowed_tools": ["http", "memory_read"]
  }
}
```

## Resource Limits

```rust theme={null}
pub struct ResourceLimits {
    pub memory_bytes: u64,
    pub fuel_limit: u64,
    pub timeout_ms: u64,
}

impl Default for ResourceLimits {
    fn default() -> Self {
        Self {
            memory_bytes: DEFAULT_MEMORY_LIMIT,  // 10 MB
            fuel_limit: DEFAULT_FUEL_LIMIT,      // 100M ops
            timeout_ms: DEFAULT_TIMEOUT,         // 30 seconds
        }
    }
}
```

**Location**: `src/tools/wasm/limits.rs:10-42`

**Constants**:

```rust theme={null}
pub const DEFAULT_MEMORY_LIMIT: u64 = 10 * 1024 * 1024;  // 10 MB
pub const DEFAULT_FUEL_LIMIT: u64 = 100_000_000;         // 100M operations
pub const DEFAULT_TIMEOUT: u64 = 30_000;                 // 30 seconds
```

**Location**: `src/tools/wasm/limits.rs:7-9`

## Credential Injection

WASM tools never see raw credentials. The host injects them at request time.

**Location**: `src/tools/wasm/credential_injector.rs`

### Credential Mapping

```rust theme={null}
pub struct CredentialMapping {
    pub secret_name: String,
    pub location: CredentialLocation,
    pub host_patterns: Vec<String>,
}

pub enum CredentialLocation {
    Bearer,                           // Authorization: Bearer <token>
    Header { name: String },          // Custom-Header: <token>
    QueryParam { name: String },      // ?api_key=<token>
    UrlPlaceholder { placeholder: String },  // https://api.com/{TOKEN}/data
}
```

**Example injection locations**:

```json theme={null}
{
  "credentials": {
    "github_token": {
      "secret_name": "github_token",
      "location": {"type": "bearer"},
      "host_patterns": ["api.github.com"]
    },
    "telegram_token": {
      "secret_name": "telegram_bot_token",
      "location": {
        "type": "url_placeholder",
        "placeholder": "BOT_TOKEN"
      },
      "host_patterns": ["api.telegram.org"]
    },
    "custom_api_key": {
      "secret_name": "api_key",
      "location": {
        "type": "query_param",
        "name": "api_key"
      },
      "host_patterns": ["api.example.com"]
    }
  }
}
```

**Location**: `src/tools/wasm/credential_injector.rs:28-150`

### Placeholder Substitution

Tools can reference credentials by placeholder in URLs and headers:

```rust theme={null}
// In WASM tool code:
let url = "https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage";
let headers = json!({
    "Authorization": "Bearer {GITHUB_TOKEN}"
});

// Host substitutes before making request
let injected_url = "https://api.telegram.org/bot1234567890:ABC.../sendMessage";
let injected_headers = {"Authorization": "Bearer ghp_abc123..."};
```

**Location**: `src/tools/wasm/wrapper.rs:129-145`

## Leak Detection

All WASM tool outputs are scanned for credential leakage:

```rust theme={null}
let leak_detector = LeakDetector::new();
for cred in &self.credentials {
    leak_detector.add_secret(&cred.secret_value);
}
let sanitized = leak_detector.scan(&output)?;
```

**Location**: `src/tools/wasm/wrapper.rs:300-320`

Secrets are replaced with `[REDACTED:SECRET_NAME]` before returning to the LLM.

## Building a WASM Tool

### 1. Create Project

```bash theme={null}
cd tools-src
cargo new --lib my-tool
cd my-tool
```

### 2. Configure Cargo.toml

```toml theme={null}
[package]
name = "my-tool"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
wit-bindgen = "0.28"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
strip = true
```

### 3. Implement Tool

```rust theme={null}
//! My custom WASM tool.

wit_bindgen::generate!({
    world: "sandboxed-tool",
    path: "../../wit/tool.wit",
});

use serde::{Deserialize, Serialize};

struct MyTool;

#[derive(Deserialize)]
struct Params {
    input: String,
}

#[derive(Serialize)]
struct Output {
    result: String,
}

impl exports::near::agent::tool::Guest for MyTool {
    fn name() -> String {
        "my_tool".to_string()
    }

    fn description() -> String {
        "Does something useful".to_string()
    }

    fn parameters_schema() -> String {
        serde_json::json!({
            "type": "object",
            "properties": {
                "input": {
                    "type": "string",
                    "description": "Input text"
                }
            },
            "required": ["input"]
        }).to_string()
    }

    fn execute(params_json: String) -> Result<String, String> {
        let params: Params = serde_json::from_str(&params_json)
            .map_err(|e| format!("Invalid params: {}", e))?;

        // Use host functions
        near::agent::host::log(
            near::agent::host::LogLevel::Info,
            format!("Processing: {}", params.input),
        );

        // Make HTTP request (if allowed)
        let response = near::agent::host::http_request(
            "GET".to_string(),
            "https://api.github.com/zen".to_string(),
            "{}".to_string(),
            None,
            Some(5000),
        ).map_err(|e| format!("HTTP error: {}", e))?;

        let result = String::from_utf8_lossy(&response.body).to_string();

        let output = Output { result };
        Ok(serde_json::to_string(&output).unwrap())
    }
}

export!(MyTool);
```

### 4. Create Capabilities File

`my-tool.capabilities.json`:

```json theme={null}
{
  "http": {
    "allowlist": [
      {
        "host": "api.github.com",
        "path_prefix": "/",
        "methods": ["GET"]
      }
    ],
    "rate_limit": {
      "requests_per_minute": 60,
      "requests_per_hour": 1000
    }
  }
}
```

### 5. Build

```bash theme={null}
cargo build --target wasm32-wasip2 --release
```

Output: `target/wasm32-wasip2/release/my_tool.wasm`

### 6. Install

```bash theme={null}
ironclaw tool install target/wasm32-wasip2/release/my_tool.wasm \
  --capabilities my-tool.capabilities.json
```

## Runtime APIs

### WasmToolRuntime

**Location**: `src/tools/wasm/runtime.rs`

```rust theme={null}
pub struct WasmToolRuntime {
    engine: Engine,
    linker: Arc<Linker<StoreData>>,
}

impl WasmToolRuntime {
    pub fn new(config: WasmRuntimeConfig) -> Result<Self, WasmError>;
    
    pub async fn prepare(
        &self,
        name: &str,
        wasm_bytes: &[u8],
        limits: Option<ResourceLimits>,
    ) -> Result<PreparedModule, WasmError>;
}
```

**Location**: `src/tools/wasm/runtime.rs:52-150`

### WasmToolWrapper

**Location**: `src/tools/wasm/wrapper.rs`

```rust theme={null}
pub struct WasmToolWrapper {
    runtime: Arc<WasmToolRuntime>,
    module: PreparedModule,
    capabilities: Capabilities,
    description_override: Option<String>,
    schema_override: Option<serde_json::Value>,
    secrets_store: Option<Arc<dyn SecretsStore>>,
    oauth_refresh: Option<OAuthRefreshConfig>,
}

impl WasmToolWrapper {
    pub fn new(
        runtime: Arc<WasmToolRuntime>,
        module: PreparedModule,
        capabilities: Capabilities,
    ) -> Self;
    
    pub fn with_description(mut self, desc: impl Into<String>) -> Self;
    pub fn with_schema(mut self, schema: serde_json::Value) -> Self;
    pub fn with_secrets_store(mut self, store: Arc<dyn SecretsStore>) -> Self;
    pub fn with_oauth_refresh(mut self, config: OAuthRefreshConfig) -> Self;
}
```

**Location**: `src/tools/wasm/wrapper.rs:67-98`

## Storage

WASM tools can be stored in a database with integrity verification.

**Location**: `src/tools/wasm/storage.rs`

### Store Tool

```rust theme={null}
pub async fn store_tool(
    &self,
    params: StoreToolParams<'_>,
) -> Result<StoredWasmTool, WasmStorageError>;

pub struct StoreToolParams<'a> {
    pub user_id: &'a str,
    pub name: &'a str,
    pub description: &'a str,
    pub wasm_binary: &'a [u8],
    pub parameters_schema: serde_json::Value,
    pub trust_level: TrustLevel,
    pub capabilities: Option<StoredCapabilities>,
}
```

### Load Tool

```rust theme={null}
pub async fn get_with_binary(
    &self,
    user_id: &str,
    name: &str,
) -> Result<StoredWasmToolWithBinary, WasmStorageError>;
```

**Integrity verification**:

* BLAKE3 hash computed on store
* Hash verified on load
* Prevents WASM tampering

**Location**: `src/tools/wasm/storage.rs:120-280`

## Tool Discovery

Discover WASM tools in the `tools-src/` directory:

**Location**: `src/tools/wasm/loader.rs`

```rust theme={null}
pub fn discover_tools(tools_dir: &Path) -> Vec<DiscoveredTool>;

pub struct DiscoveredTool {
    pub name: String,
    pub wasm_path: PathBuf,
    pub capabilities_path: Option<PathBuf>,
}
```

**Example**:

```rust theme={null}
let tools = discover_tools(Path::new("tools-src"));
for tool in tools {
    println!("Found: {} at {:?}", tool.name, tool.wasm_path);
}
```

**Location**: `src/tools/wasm/loader.rs:28-150`

## OAuth Authentication

WASM tools can declare OAuth requirements in capabilities:

```json theme={null}
{
  "auth": {
    "secret_name": "slack_bot_token",
    "display_name": "Slack",
    "oauth": {
      "authorization_url": "https://slack.com/oauth/v2/authorize",
      "token_url": "https://slack.com/api/oauth.v2.access",
      "client_id_env": "SLACK_OAUTH_CLIENT_ID",
      "client_secret_env": "SLACK_OAUTH_CLIENT_SECRET",
      "scopes": ["chat:write", "channels:read"],
      "use_pkce": false
    },
    "env_var": "SLACK_BOT_TOKEN"
  }
}
```

**Auth flow priority**:

1. Check `env_var` - if set in environment, use it directly
2. Check `oauth` - if configured, open browser for OAuth flow
3. Fall back to `instructions` + manual token entry

**Location**: See `src/tools/README.md:49-103`

## Examples

Real-world WASM tools in `tools-src/`:

* **GitHub** (`tools-src/github/`) - Repository management, issues, PRs, workflows
* **Gmail** (`tools-src/gmail/`) - Search, read, send emails
* **Slack** (`tools-src/slack/`) - Post messages, read channels
* **Telegram** (`tools-src/telegram/`) - Send messages, manage conversations
* **Google Calendar** (`tools-src/google-calendar/`) - Manage events
* **Google Drive** (`tools-src/google-drive/`) - File management
* **Google Sheets** (`tools-src/google-sheets/`) - Spreadsheet operations
* **Google Docs** (`tools-src/google-docs/`) - Document editing

## Best Practices

1. **Minimize allowlist scope**: Only allow the specific hosts/paths your tool needs
2. **Use credential injection**: Never hardcode secrets
3. **Validate inputs**: Check parameter lengths and formats
4. **Handle errors gracefully**: Return descriptive error messages
5. **Log appropriately**: Use host `log()` for debugging
6. **Optimize binary size**: Use `opt-level = "z"` and `strip = true`
7. **Test thoroughly**: Test with and without credentials
8. **Document schema**: Provide clear descriptions for all parameters

## Next Steps

<CardGroup cols={2}>
  <Card title="MCP Integration" icon="plug" href="/tools/mcp">
    Learn about MCP server integration
  </Card>

  <Card title="Building Tools" icon="hammer" href="/tools/building">
    Use the software builder to create tools
  </Card>
</CardGroup>
