Summary
This document proposes a comprehensive design for implementing configurable, portable WASM-based sandboxing for plugins across multiple programming languages (Python, Go, Rust) and operating systems. The solution provides fine-grained security controls while maintaining performance and developer experience.
Key Features:
- Configurable per-plugin security policies via YAML
- Cross-language portability (Python, Go, Rust)
- Capability-based security with zero-trust model
- Near-native performance with minimal overhead
- Support for both WASM Component Model and Core WASM
Motivation
Problem Statement
Modern plugin systems face critical security challenges:
- Native plugins run with full process privileges, accessing gateway internals without restrictions
- External plugins in Docker containers are isolated but heavyweight and have deployment complexity
- Lack of granular control over individual plugin capabilities (filesystem, network, memory)
- No portable solution that works consistently across languages and operating systems
Why WASM Sandboxing?
WebAssembly provides:
- Capability-based security: Explicit permission grants for resources
- Memory isolation: Linear memory model prevents buffer overflows
- Platform independence: Same sandbox works on Linux, macOS, Windows
- Near-native performance: Minimal overhead compared to containers
- Language agnostic: Compile from any language to WASM
Design
Design Goals & Requirements
Functional Requirements
FR1: Policy Configuration
- Define security policies in YAML per plugin
- Support filesystem, network, environment, and compute policies
- Allow policy inheritance and composition
FR2: Multi-Language Support
- Host support: Rust
- Guest support: Python, Go and Rust
FR3: Sandbox Management
- Load and execute plugins in isolated WASM instances
- Handle plugin dependencies and recursive calls
- Manage sandbox lifecycle (create, execute, destroy)
FR4: Resource Control
- Enforce memory limits
- Implement execution timeouts
- Control CPU usage
FR5: Capability-Based Security
- Explicit grants for filesystem paths
- Network endpoint allowlisting
- Environment variable filtering
Design Decisions
WASM Runtime Selection
Component Model vs Core WASM Decision Matrix
| Criteria |
Core WASM |
Component Model |
Our Choice |
| Performance |
⭐⭐⭐⭐⭐ |
⭐⭐⭐⭐ |
Hybrid |
| Type Safety |
⭐⭐ |
⭐⭐⭐⭐⭐ |
Component |
| Composition |
⭐ |
⭐⭐⭐⭐⭐ |
Component |
| Simplicity |
⭐⭐⭐⭐ |
⭐⭐⭐ |
Core |
| Maturity |
⭐⭐⭐⭐⭐ |
⭐⭐⭐ |
Core |
| Future-Ready |
⭐⭐ |
⭐⭐⭐⭐⭐ |
Component |
| Multi-Language |
⭐⭐ |
⭐⭐⭐⭐⭐ |
Component |
Use Component Model When:
- Complex Plugin Systems - Multiple plugins need to communicate
- Rich Data Types - Passing complex structures
- Language Interoperability - Plugins in different languages
- Future-Proofing - Want WASI Preview 2 features
- Better Developer Experience - Type-safe interfaces
Use Core WASM When:
- Maximum Performance - Compute-intensive tasks
- Simple, Isolated Plugins - Single-purpose functions
- Existing Ecosystem - Large Core WASM codebase
- Minimal Dependencies - Simple I/O requirements
- Embedded Systems - Resource-constrained environments
Support for both is chosen because Component Model is still maturing—core WASM provides stable, production-ready compatibility today while Component Model offers a future-proof migration path as tooling and ecosystem adoption improve.
Architecture Overview
High-Level Architecture
┌──────────────────────────────────────────────────────────────────────────────┐
│ Gateway │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ Plugin Manager (Host Language) │ │
│ │ - Load plugin configurations │ │
│ │ - Invoke plugin hooks │ │
│ │ - Manage plugin lifecycle │ │
│ └───────────────────────────────┬────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────────▼────────────────────────────────────────┐ │
│ │ Sandbox Manager (Rust Core) │ │
│ │ - Parse security policies │ │
│ │ - Create WASM instances with defined policies │ │
│ │ - Enforce resource limits │ │
│ │ - Handle recursive plugin calls or dependencies │ │
│ └───────────────────────────────┬────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────────▼────────────────────────────────────────┐ │
│ │ WASM Runtime (Wasmtime) │ │
│ │ - Execute WASM bytecode │ │
│ │ - Provide WASI interface │ │
│ │ - Enforce memory isolation │ │
│ └───────────────────────────────┬────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────────▼────────────────────────────────────────┐ │
│ │ Capability Provider Layer │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Filesystem │ │ Network │ │ Env │ │ │
│ │ │ Provider │ │ Provider │ │ Provider │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────────────┐ │
│ │ Plugin Instances (WASM) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Plugin A │ │ Plugin B │ │ Plugin C │ │ │
│ │ │ (Auth) │ │ (Data) │ │ (UI) │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └────────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
Configuration
YAML Schema Example
name: "DenyListPlugin"
kind: "plugins.deny_filter.deny.DenyListPlugin"
hooks: ["prompt_pre_fetch"]
mode: "enforce"
priority: 100
config:
words: ["innovative", "groundbreaking", "revolutionary"]
sandbox:
type: wasm
wasm:
format: component # or "core"
# Filesystem access permissions
filesystem:
# Directories that can be read from
read_only:
- path: "./data/input"
guest_path: "/input"
description: "Input data directory (read-only)"
# Directories that can be written to
read_write:
- path: "./data/output"
guest_path: "/output"
description: "Output data directory (read-write)"
# Network access permissions
network:
allowed_hosts:
- "api.example.com"
- "httpbin.org"
- "localhost"
# Allowed ports
allowed_ports:
- 80
- 443
- 8080
# Environment variables accessible to the child process
environment:
# Inherit from parent
inherit:
- "PATH"
- "HOME"
# Resource limits
resources:
# Maximum memory allocation (in MB)
max_memory_mb: 128
# Maximum execution time (in seconds)
max_execution_time_sec: 30
# Maximum number of file descriptors
max_file_descriptors: 100
# Capabilities and permissions
capabilities:
# Allow access to system clock
clock_access: true
# Allow random number generation
random_access: true
# Allow command-line arguments
args_access: true
# Allow stdin/stdout/stderr
stdio_access: true
# Logging configuration
logging:
enabled: true
level: "info"
log_file: "./sandbox.log"
The configuration for sandbox could be defined by user inside the key sandbox where the type of sandboxing, in this case wasm could be specified. Within the wasm key the format which could either be core or component could be specified, and then policies within the keys filesystem, network, environment, resources and capabilities could be specified.
The plugin manager communicates with the sandbox manager, which is responsible for creating and managing isolated environments for each plugin instance based on the policies defined in the plugin sandbox configuration. The sandbox manager interprets these policies, initializes WebAssembly (Wasm) instances accordingly, and enforces resource limits using the WASI context.
It sets up a Wasmtime engine with resource constraints and backtrace support, and configures the WASI interface with features like standard I/O inheritance, environment variables, and preopened directories using use wasmtime_wasi::WasiCtxBuilder. The manager then loads the plugin.wasm file, instantiates it within the sandbox, executes it under the defined restrictions, monitors resource (fuel) usage, and reports whether execution succeeds or fails.
The plugin.wasm is pre-compiled using wasm32-wasip1 (core) or wasm32-wasip2 (component). If using core, then direct compilation with rustc to WASI Preview 1. However, if using the component setup then it is compiled using the component model using cargo-component.
📝 NOTE: WASI Preview 1 (wasm32-wasip1) has limited networking support. WASI Preview 2 (Wasm component model) or custom host functions is needed.
Interaction Flow
-
Plugin Configuration Load
Plugin Manager → Parse YAML → Extract Policies
-
Sandbox Creation
Plugin Manager → Sandbox Manager → Create WASM Instance
→ Configure Capabilities → Return Sandbox Handle
-
Plugin Execution
Hook Trigger → Plugin Manager → Sandbox Manager
→ WASM Runtime → Execute Plugin Code → Return Result
-
Capability Access
Plugin Code → WASI Call → Capability Provider
→ Policy Check → Grant/Deny → Return Result
-
Recursive Plugin Call
Plugin A → Sandbox Manager → Check Dependency Graph
→ Create/Reuse Sandbox B → Execute → Return to A
📝 NOTE: For supporting recursive plugin calls (plugins calling other plugins), the Component Model is required. Core WASM does not support this capability.
Sandbox Manager
Runs natively on your system with full privileges, while the plugin (plugin.wasm) runs in a restricted WASM sandbox with only explicitly granted capabilities.
Responsibilities:
- Parse and validate security policies
- Create and configure WASM instances
- Manage sandbox lifecycle
- Handle inter-plugin communication
- Enforce resource limits
- Provide audit logging
fn main() -> Result<()> {
// 1. Load security configuration
let config = load_config("sandbox_config.yaml")?;
// 2. Create WASM engine with limits
let engine = Engine::new(&engine_config)?;
// 3. Load the plugin module
let module = Module::from_file(&engine, "plugin.wasm")?;
// 4. Build sandboxed WASI context
let wasi_ctx = build_wasi_context(&config, &child_args)?;
// 5. Create store with fuel limits
let mut store = Store::new(&engine, wasi_ctx);
store.set_fuel(config.resources.max_execution_time_sec * 1_000_000)?;
// 6. Execute the plugin
let instance = linker.instantiate(&mut store, &module)?;
start.call(&mut store, ())?;
}
Policy
Defines sandbox constraints:
pub struct SandboxPolicy {
pub name: String,
pub compute: Compute, // CPU fuel allocation
pub ram: Option<u64>, // Memory limit in bytes
pub timeout: Option<String>, // Max execution time
pub max_retries: u64, // Retry attempts on failure
pub allowed_files: Vec<...>, // Filesystem access rules
pub allowed_hosts: Vec<...>, // Network access whitelist
pub env_variables: Vec<...>, // Environment variable access
pub mounts: Vec<...>, // Dynamic directory mappings
}
Sandbox manager to binary compilation
cargo build --release --bin sandboxmanager
Plugin to Wasm Compilation
Makefile that compiles plugin.rs with both Core WASM and Component Model WASM:
- Core WASM: Compiles using
rustc --target wasm32-wasip1
- Component Model: Compiles using
cargo component build --target wasm32-wasip2
- Automatic dependency checking and installation
- Builds sandbox manager binaries for both implementations
Main Commands:
make all - Build both WASM formats
make core - Build Core WASM only
make component - Build Component Model only
Phased implementation
Phase 1: Core Wasm (Rust): Implement the proposed architecture for sandbox manager with end-to-end functionality from plugin manager to sandboxed plugin execution, including robust error handling and test cases using Rust and Core Wasm. Some limitations might be encountered due to compatibility issues or limited functionality of wasm32-wasip1, but most of it could be addressed using wasm32-wasip2 in phase 2.
Phase 2: Component Wasm (Rust): Implement the WIT interfaces and add component model support to the sandbox manager.
Phase 3: Recursive Composition (Rust): Design and implement plugin dependency resolution, leveraging the Wasm component model to support recursive composition.
Phase 4: Extend plugin wasm compilation support beyond Rust to include Go and Python.
Dependencies
wasmtime - WebAssembly runtime
wasmtime-wasi - WebAssembly System Interface
wasmtime-wasi-http WASI HTTP interface for the Wasmtime WebAssembly runtime
Risks
The Rust-based ecosystem for WebAssembly is mature across both the core and component models, with well-established tooling such as wasmtime, wasm-tools, and cargo component providing a reliable path from source to sandboxed execution. However, compiling plugins written in Python or Go to Wasm introduces significant friction, as the tooling for these languages is considerably less mature and imposes constraints that can be difficult to work around in practice.
-
Preliminary experiments were conducted with componentize-py to compile Python-based plugins into Wasm components. While basic compilation succeeded, we encountered compatibility issues with pydantic — a dependency commonly used across the plugin ecosystem for data validation and schema definition. These issues stem from pydantic's reliance on native C extensions and runtime type introspection, which do not translate cleanly to the Wasm component model's static interface constraints defined via WIT. Similar limitations are likely to surface with other Python libraries that depend on native code or dynamic features not well-supported by the current componentize-py toolchain.
-
For Go, compilation to Wasm components is possible via TinyGo, but it comes with its own set of restrictions — limited standard library coverage, constraints on reflection and goroutines, and divergence from upstream Go semantics — which would need to be evaluated before committing to it as a supported path.
Summary
This document proposes a comprehensive design for implementing configurable, portable WASM-based sandboxing for plugins across multiple programming languages (Python, Go, Rust) and operating systems. The solution provides fine-grained security controls while maintaining performance and developer experience.
Key Features:
Motivation
Problem Statement
Modern plugin systems face critical security challenges:
Why WASM Sandboxing?
WebAssembly provides:
Design
Design Goals & Requirements
Functional Requirements
FR1: Policy Configuration
FR2: Multi-Language Support
FR3: Sandbox Management
FR4: Resource Control
FR5: Capability-Based Security
Design Decisions
WASM Runtime Selection
Component Model vs Core WASM Decision Matrix
Use Component Model When:
Use Core WASM When:
Support for both is chosen because Component Model is still maturing—core WASM provides stable, production-ready compatibility today while Component Model offers a future-proof migration path as tooling and ecosystem adoption improve.
Architecture Overview
High-Level Architecture
Configuration
YAML Schema Example
The configuration for sandbox could be defined by user inside the key
sandboxwhere the type of sandboxing, in this case wasm could be specified. Within thewasmkey the format which could either becoreorcomponentcould be specified, and then policies within the keysfilesystem,network,environment,resourcesandcapabilitiescould be specified.The plugin manager communicates with the sandbox manager, which is responsible for creating and managing isolated environments for each plugin instance based on the policies defined in the plugin sandbox configuration. The sandbox manager interprets these policies, initializes WebAssembly (Wasm) instances accordingly, and enforces resource limits using the WASI context.
It sets up a Wasmtime engine with resource constraints and backtrace support, and configures the WASI interface with features like standard I/O inheritance, environment variables, and preopened directories using use
wasmtime_wasi::WasiCtxBuilder. The manager then loads theplugin.wasmfile, instantiates it within the sandbox, executes it under the defined restrictions, monitors resource (fuel) usage, and reports whether execution succeeds or fails.The
plugin.wasmis pre-compiled using wasm32-wasip1 (core) or wasm32-wasip2 (component). If using core, then direct compilation withrustcto WASI Preview 1. However, if using the component setup then it is compiled using the component model usingcargo-component.Interaction Flow
Plugin Configuration Load
Plugin Manager → Parse YAML → Extract Policies
Sandbox Creation
Plugin Manager → Sandbox Manager → Create WASM Instance
→ Configure Capabilities → Return Sandbox Handle
Plugin Execution
Hook Trigger → Plugin Manager → Sandbox Manager
→ WASM Runtime → Execute Plugin Code → Return Result
Capability Access
Plugin Code → WASI Call → Capability Provider
→ Policy Check → Grant/Deny → Return Result
Recursive Plugin Call
Plugin A → Sandbox Manager → Check Dependency Graph
→ Create/Reuse Sandbox B → Execute → Return to A
Sandbox Manager
Runs natively on your system with full privileges, while the plugin (
plugin.wasm) runs in a restricted WASM sandbox with only explicitly granted capabilities.Responsibilities:
Policy
Defines sandbox constraints:
Sandbox manager to binary compilation
cargo build --release --bin sandboxmanagerPlugin to Wasm Compilation
Makefile that compiles
plugin.rswith both Core WASM and Component Model WASM:rustc --target wasm32-wasip1cargo component build --target wasm32-wasip2Main Commands:
make all- Build both WASM formatsmake core- Build Core WASM onlymake component- Build Component Model onlyPhased implementation
Phase 1: Core Wasm (Rust): Implement the proposed architecture for sandbox manager with end-to-end functionality from plugin manager to sandboxed plugin execution, including robust error handling and test cases using Rust and Core Wasm. Some limitations might be encountered due to compatibility issues or limited functionality of
wasm32-wasip1, but most of it could be addressed usingwasm32-wasip2in phase 2.Phase 2: Component Wasm (Rust): Implement the WIT interfaces and add component model support to the sandbox manager.
Phase 3: Recursive Composition (Rust): Design and implement plugin dependency resolution, leveraging the Wasm component model to support recursive composition.
Phase 4: Extend plugin wasm compilation support beyond Rust to include Go and Python.
Dependencies
wasmtime - WebAssembly runtime
wasmtime-wasi - WebAssembly System Interface
wasmtime-wasi-http WASI HTTP interface for the Wasmtime WebAssembly runtime
Risks
The Rust-based ecosystem for WebAssembly is mature across both the core and component models, with well-established tooling such as wasmtime, wasm-tools, and cargo component providing a reliable path from source to sandboxed execution. However, compiling plugins written in Python or Go to Wasm introduces significant friction, as the tooling for these languages is considerably less mature and imposes constraints that can be difficult to work around in practice.
Preliminary experiments were conducted with componentize-py to compile Python-based plugins into Wasm components. While basic compilation succeeded, we encountered compatibility issues with pydantic — a dependency commonly used across the plugin ecosystem for data validation and schema definition. These issues stem from pydantic's reliance on native C extensions and runtime type introspection, which do not translate cleanly to the Wasm component model's static interface constraints defined via WIT. Similar limitations are likely to surface with other Python libraries that depend on native code or dynamic features not well-supported by the current
componentize-pytoolchain.For Go, compilation to Wasm components is possible via TinyGo, but it comes with its own set of restrictions — limited standard library coverage, constraints on reflection and goroutines, and divergence from upstream Go semantics — which would need to be evaluated before committing to it as a supported path.