Skip to main content

Overview

The WASM tool system provides secure sandboxed execution for untrusted tools using Wasmtime. It follows patterns from NEAR blockchain and modern WASM best practices:
  • Compile once, instantiate fresh: Tools are validated and compiled at registration time. Each execution creates a fresh instance.
  • Fuel metering: CPU usage is limited via Wasmtime’s fuel system.
  • Memory limits: Memory growth is bounded via ResourceLimiter.
  • Capability-based security: Features are opt-in via Capabilities.
  • Extended host API: Log, time, workspace, HTTP, tool invoke, secrets.

Architecture

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

Security Constraints

ThreatMitigation
CPU exhaustionFuel metering
Memory exhaustionResourceLimiter, 10MB default
Infinite loopsEpoch interruption + tokio timeout
Filesystem accessNo WASI FS, only host workspace_read
Network accessAllowlisted endpoints only
Credential exposureInjection at host boundary only
Secret exfiltrationLeak detector scans all outputs
Log spamMax 1000 entries, 4KB per message
Path traversalValidate paths (no .., no / prefix)
Trap recoveryDiscard instance, never reuse
Side channelsFresh instance per execution
Rate abusePer-tool rate limiting
WASM tamperingBLAKE3 hash verification on load

WasmToolRuntime

The runtime manages WASM compilation and execution.

Constructor

src/tools/wasm/runtime.rs
pub fn new(config: WasmRuntimeConfig) -> Result<Self, WasmError>
Creates a new WASM runtime with the specified configuration.
config
WasmRuntimeConfig
required
Runtime configuration including engine settings and security limits
Returns: Result<WasmToolRuntime, WasmError>

prepare

src/tools/wasm/runtime.rs
pub async fn prepare(
    &self,
    name: &str,
    wasm_bytes: &[u8],
    limits: Option<ResourceLimits>,
) -> Result<PreparedModule, WasmError>
Validates and compiles a WASM component. This is done once at registration time.
name
&str
required
Tool name for error messages
wasm_bytes
&[u8]
required
Raw WASM component bytes
limits
Option<ResourceLimits>
Optional resource limits (uses defaults if None)
Returns: Result<PreparedModule, WasmError> - Compiled module ready for execution

WasmToolWrapper

Wraps a WASM component as a Tool implementation.

Constructor

src/tools/wasm/wrapper.rs
pub fn new(
    runtime: Arc<WasmToolRuntime>,
    prepared: PreparedModule,
    capabilities: Capabilities,
) -> Self
Creates a new WASM tool wrapper.
runtime
Arc<WasmToolRuntime>
required
Shared runtime for execution
prepared
PreparedModule
required
Pre-compiled module from runtime.prepare()
capabilities
Capabilities
required
Security capabilities to grant the tool

Configuration Methods

src/tools/wasm/wrapper.rs
pub fn with_description(mut self, description: 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 + Send + Sync>) -> Self
pub fn with_oauth_refresh(mut self, config: OAuthRefreshConfig) -> Self
Optional configuration overrides.

Capabilities

Defines what host functions a WASM tool can access.
src/tools/wasm/capabilities.rs
pub struct Capabilities {
    pub http: Option<HttpCapability>,
    pub workspace: Option<WorkspaceCapability>,
    pub tool_invoke: Option<ToolInvokeCapability>,
    pub secrets: Option<SecretsCapability>,
}

Constructor

src/tools/wasm/capabilities.rs
pub fn none() -> Self
Creates a capabilities set with all features disabled (log and time are always available).

Configuration Methods

src/tools/wasm/capabilities.rs
pub fn with_http(mut self, http: HttpCapability) -> Self
pub fn with_workspace(mut self, workspace: WorkspaceCapability) -> Self
pub fn with_tool_invoke(mut self, tool_invoke: ToolInvokeCapability) -> Self
pub fn with_secrets(mut self, secrets: SecretsCapability) -> Self

HttpCapability

Allows HTTP requests to specific endpoints.
src/tools/wasm/capabilities.rs
pub struct HttpCapability {
    pub allowed_endpoints: Vec<EndpointPattern>,
    pub rate_limit: Option<RateLimitConfig>,
    pub credentials: HashMap<String, crate::secrets::CredentialMapping>,
}

Constructor

src/tools/wasm/capabilities.rs
pub fn new(allowed_endpoints: Vec<EndpointPattern>) -> Self
allowed_endpoints
Vec<EndpointPattern>
required
List of allowed URL patterns

EndpointPattern

Defines an allowed HTTP endpoint pattern.
src/tools/wasm/capabilities.rs
pub struct EndpointPattern {
    pub host: String,
    pub port: Option<u16>,
    pub path_prefix: Option<String>,
    pub allowed_methods: Vec<String>,
}

Constructors

src/tools/wasm/capabilities.rs
pub fn host(host: impl Into<String>) -> Self
pub fn localhost(port: u16) -> Self

pub fn with_port(mut self, port: u16) -> Self
pub fn with_path_prefix(mut self, prefix: impl Into<String>) -> Self
pub fn with_methods(mut self, methods: Vec<String>) -> Self
Example:
let pattern = EndpointPattern::host("api.openai.com")
    .with_path_prefix("/v1/")
    .with_methods(vec!["GET".into(), "POST".into()]);

WorkspaceCapability

Allows access to workspace files.
src/tools/wasm/capabilities.rs
pub struct WorkspaceCapability {
    pub reader: WorkspaceReader,
    pub allowed_paths: Option<Vec<String>>,
}

Constructor

src/tools/wasm/capabilities.rs
pub fn new(reader: WorkspaceReader) -> Self
reader
WorkspaceReader
required
Workspace reader for file access

Configuration

src/tools/wasm/capabilities.rs
pub fn with_allowed_paths(mut self, paths: Vec<String>) -> Self
Restricts access to specific paths only.

ResourceLimits

Defines execution resource limits.
src/tools/wasm/limits.rs
pub struct ResourceLimits {
    pub max_memory_bytes: usize,
    pub fuel_config: FuelConfig,
    pub max_execution_time: Duration,
}
max_memory_bytes
usize
default:"10MB"
Maximum memory the WASM instance can allocate
fuel_config
FuelConfig
Fuel-based CPU metering configuration
max_execution_time
Duration
default:"30s"
Maximum execution time before timeout

Defaults

src/tools/wasm/limits.rs
pub const DEFAULT_MEMORY_LIMIT: usize = 10 * 1024 * 1024; // 10 MB
pub const DEFAULT_FUEL_LIMIT: u64 = 200_000_000; // ~100ms on modern CPU
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);

Storage

Persist WASM tools to the database.

WasmToolStore Trait

src/tools/wasm/storage.rs
#[async_trait]
pub trait WasmToolStore: Send + Sync {
    async fn store(&self, params: StoreToolParams) -> Result<StoredWasmTool, WasmStorageError>;
    async fn get(&self, user_id: &str, name: &str) -> Result<StoredWasmTool, WasmStorageError>;
    async fn get_with_binary(&self, user_id: &str, name: &str) -> Result<StoredWasmToolWithBinary, WasmStorageError>;
    async fn list(&self, user_id: &str) -> Result<Vec<StoredWasmTool>, WasmStorageError>;
    async fn delete(&self, user_id: &str, name: &str) -> Result<(), WasmStorageError>;
    async fn store_capabilities(&self, tool_id: Uuid, caps: StoredCapabilities) -> Result<(), WasmStorageError>;
    async fn get_capabilities(&self, tool_id: Uuid) -> Result<Option<StoredCapabilities>, WasmStorageError>;
}

StoreToolParams

src/tools/wasm/storage.rs
pub struct StoreToolParams {
    pub user_id: String,
    pub name: String,
    pub description: String,
    pub wasm_binary: Vec<u8>,
    pub parameters_schema: serde_json::Value,
    pub trust_level: TrustLevel,
    pub source: String,
}

Error Types

WasmError

src/tools/wasm/error.rs
pub enum WasmError {
    CompilationFailed(String),
    InstantiationFailed(String),
    ExecutionFailed(String),
    Trapped(TrapInfo),
    Timeout,
    FuelExhausted,
    InvalidComponent(String),
    InvalidParameters(String),
    CapabilityDenied(String),
}

TrapInfo

src/tools/wasm/error.rs
pub struct TrapInfo {
    pub code: TrapCode,
    pub message: String,
    pub backtrace: Option<String>,
}

Example: Register WASM Tool

use ironclaw::tools::wasm::{
    WasmToolRuntime, WasmRuntimeConfig, WasmToolWrapper,
    Capabilities, HttpCapability, EndpointPattern,
};
use std::sync::Arc;

// Create runtime
let runtime = Arc::new(WasmToolRuntime::new(WasmRuntimeConfig::default())?);

// Load WASM bytes
let wasm_bytes = std::fs::read("my_tool.wasm")?;

// Prepare the module
let prepared = runtime.prepare("my_tool", &wasm_bytes, None).await?;

// Configure capabilities
let capabilities = Capabilities::none()
    .with_http(HttpCapability::new(vec![
        EndpointPattern::host("api.example.com")
            .with_path_prefix("/v1/")
            .with_methods(vec!["GET".into(), "POST".into()]),
    ]));

// Create wrapper
let tool = WasmToolWrapper::new(runtime, prepared, capabilities)
    .with_description("My custom WASM tool");

// Register with tool registry
registry.register(Arc::new(tool)).await;

Example: Store and Load

use ironclaw::tools::wasm::{
    PostgresWasmToolStore, StoreToolParams, TrustLevel,
};

// Create store
let store = PostgresWasmToolStore::new(pool);

// Store tool
let tool = store.store(StoreToolParams {
    user_id: "user_123".into(),
    name: "my_tool".into(),
    description: "Does something useful".into(),
    wasm_binary: wasm_bytes,
    parameters_schema: schema,
    trust_level: TrustLevel::Trusted,
    source: "local".into(),
}).await?;

// Load and register
registry.register_wasm_from_storage(
    &store,
    &runtime,
    "user_123",
    "my_tool",
).await?;

Build docs developers (and LLMs) love