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
| 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 |
| Side channels | Fresh instance per execution |
| Rate abuse | Per-tool rate limiting |
| WASM tampering | BLAKE3 hash verification on load |
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.
Tool name for error messages
Optional resource limits (uses defaults if None)
Returns: Result<PreparedModule, WasmError> - Compiled module ready for execution
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
Pre-compiled module from runtime.prepare()
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
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
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.
pub struct ResourceLimits {
pub max_memory_bytes: usize,
pub fuel_config: FuelConfig,
pub max_execution_time: Duration,
}
Maximum memory the WASM instance can allocate
Fuel-based CPU metering configuration
Maximum execution time before timeout
Defaults
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.
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>;
}
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
pub enum WasmError {
CompilationFailed(String),
InstantiationFailed(String),
ExecutionFailed(String),
Trapped(TrapInfo),
Timeout,
FuelExhausted,
InvalidComponent(String),
InvalidParameters(String),
CapabilityDenied(String),
}
TrapInfo
pub struct TrapInfo {
pub code: TrapCode,
pub message: String,
pub backtrace: Option<String>,
}
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?;