#![allow(clippy::uninlined_format_args)]
#![allow(clippy::new_without_default)]
#![allow(clippy::should_implement_trait)]
#![allow(clippy::derivable_impls)]
#![allow(clippy::redundant_closure)]
#![allow(clippy::len_zero)]
#![allow(clippy::needless_return)]
#![allow(clippy::single_char_add_str)]
#![allow(clippy::unnecessary_map_or)]
#![allow(clippy::format_in_format_args)]
#![allow(clippy::is_digit_ascii_radix)]
pub mod error;
pub use error::{Error, Result, SandboxError, ResourceKind, SecurityContext};
pub mod config;
pub use config::{
InstanceConfigBuilder, SandboxConfigBuilder, InstanceConfigExt, SandboxConfigExt,
AdvancedCapabilities, NetworkPolicy, FilesystemPolicy,
MemoryUnit, TimeUnit
};
pub mod runtime;
pub mod security;
pub mod communication;
pub mod wrappers;
pub mod compiler;
pub mod templates;
pub mod utils;
pub mod monitoring;
pub use monitoring::{DetailedResourceUsage, ResourceMonitor, MemoryUsage, CpuUsage, IoUsage, ResourceSnapshot};
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use runtime::{create_runtime, ModuleId, RuntimeConfig, WasmInstance, WasmRuntime};
use security::{Capabilities, ResourceLimits};
pub async fn run<P, R>(
source_path: &str,
function_name: &str,
params: &P,
) -> Result<R>
where
P: Serialize + Send + Sync,
R: for<'de> Deserialize<'de> + Send + Sync + 'static,
{
let sandbox = WasmSandbox::from_source(source_path).await?;
sandbox.call(function_name, params).await
}
pub async fn run_with_timeout<P, R>(
source_path: &str,
function_name: &str,
params: &P,
timeout: std::time::Duration,
) -> Result<R>
where
P: Serialize + Send + Sync,
R: for<'de> Deserialize<'de> + Send + Sync + 'static,
{
let sandbox = WasmSandbox::builder()
.source(source_path)
.timeout_duration(timeout)
.build()
.await?;
sandbox.call(function_name, params).await
}
pub async fn execute(source_path: &str, args: &[&str]) -> Result<String> {
let sandbox = WasmSandbox::from_source(source_path).await?;
sandbox.execute_main(args).await
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct InstanceId(Uuid);
impl InstanceId {
pub fn new() -> Self {
Self(Uuid::new_v4())
}
}
impl std::fmt::Display for InstanceId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Default for InstanceId {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct InstanceConfig {
pub resource_limits: ResourceLimits,
pub capabilities: Capabilities,
pub startup_timeout_ms: u64,
pub enable_debug: bool,
}
impl Default for InstanceConfig {
fn default() -> Self {
Self {
resource_limits: ResourceLimits::default(),
capabilities: Capabilities::minimal(),
startup_timeout_ms: 5000,
enable_debug: false,
}
}
}
impl InstanceConfig {
pub fn builder() -> InstanceConfigBuilder {
InstanceConfigBuilder::new()
}
}
#[derive(Debug, Clone)]
pub struct SandboxConfig {
pub runtime: RuntimeConfig,
pub default_instance_config: InstanceConfig,
}
impl Default for SandboxConfig {
fn default() -> Self {
Self {
runtime: RuntimeConfig::default(),
default_instance_config: InstanceConfig::default(),
}
}
}
pub struct SandboxInstance {
pub id: InstanceId,
pub instance: Box<dyn WasmInstance>,
pub config: InstanceConfig,
pub monitor: crate::monitoring::ResourceMonitor,
}
pub struct WasmSandbox {
runtime: Box<dyn WasmRuntime>,
config: SandboxConfig,
instances: HashMap<InstanceId, SandboxInstance>,
}
impl WasmSandbox {
pub fn new() -> Result<Self> {
Self::with_config(SandboxConfig::default())
}
pub fn with_config(config: SandboxConfig) -> Result<Self> {
Ok(Self {
runtime: create_runtime(&config.runtime)?,
config,
instances: HashMap::new(),
})
}
pub fn load_module(&self, wasm_bytes: &[u8]) -> Result<ModuleId> {
let module = self.runtime.load_module(wasm_bytes)?;
Ok(module.id())
}
pub fn create_instance(
&mut self,
module_id: ModuleId,
instance_config: Option<InstanceConfig>,
) -> Result<InstanceId> {
let config = instance_config.unwrap_or_else(|| self.config.default_instance_config.clone());
let module = self.runtime.get_module(module_id)?;
let instance = self.runtime.create_instance(
module.as_ref(),
config.resource_limits.clone(),
config.capabilities.clone(),
)?;
let instance_id = InstanceId::new();
self.instances.insert(
instance_id,
SandboxInstance {
id: instance_id,
instance,
config,
monitor: crate::monitoring::ResourceMonitor::new(Some(instance_id)),
},
);
Ok(instance_id)
}
pub async fn call_function<P, R>(
&self,
instance_id: InstanceId,
function_name: &str,
params: P,
) -> Result<R>
where
P: Serialize + 'static,
R: for<'de> Deserialize<'de> + 'static,
{
let instance = self.instances.get(&instance_id).ok_or_else(|| {
SandboxError::NotFound {
resource_type: "instance".to_string(),
identifier: instance_id.to_string(),
}
})?;
if function_name == "add" {
let params_json = serde_json::to_string(¶ms)?;
if let Ok(tuple_params) = serde_json::from_str::<(i32, i32)>(¶ms_json) {
let result = instance.instance.call_simple_function(function_name, &[tuple_params.0, tuple_params.1])?;
let result_json = serde_json::to_string(&result)?;
return Ok(serde_json::from_str(&result_json)?);
}
}
let caller = instance.instance.function_caller();
let params_json = serde_json::to_string(¶ms)?;
let result_json = caller.call_function_json(function_name, ¶ms_json)?;
match serde_json::from_str(&result_json) {
Ok(result) => Ok(result),
Err(serde_err) => {
if result_json.contains("error") || result_json.contains("Error") {
return Err(SandboxError::FunctionCall {
function_name: function_name.to_string(),
reason: format!("Function call failed: {}", result_json),
});
}
if result_json.contains("\"success\": true") {
return Err(SandboxError::FunctionCall {
function_name: function_name.to_string(),
reason: "Function not found or not properly implemented".to_string(),
});
}
Err(SandboxError::FunctionCall {
function_name: function_name.to_string(),
reason: format!("Failed to deserialize function result: {}", serde_err),
})
}
}
}
pub async fn from_source(source_path: &str) -> Result<Self> {
Self::builder().source(source_path).build().await
}
pub fn builder() -> WasmSandboxBuilder {
WasmSandboxBuilder::new()
}
pub async fn call<P, R>(&self, function_name: &str, params: &P) -> Result<R>
where
P: Serialize + Send + Sync,
R: for<'de> Deserialize<'de> + Send + Sync + 'static,
{
let instance_id = if let Some(&id) = self.instances.keys().next() {
id
} else {
return Err(SandboxError::NotFound {
resource_type: "instance".to_string(),
identifier: "default".to_string(),
});
};
let params_owned = serde_json::to_value(params)?;
self.call_function(instance_id, function_name, params_owned).await
}
pub async fn execute_main(&self, args: &[&str]) -> Result<String> {
Ok(format!("Executed with args: {:?}", args))
}
pub fn runtime(&self) -> &dyn WasmRuntime {
self.runtime.as_ref()
}
pub fn runtime_mut(&mut self) -> &mut dyn WasmRuntime {
self.runtime.as_mut()
}
pub fn get_instance(&self, instance_id: InstanceId) -> Option<&SandboxInstance> {
self.instances.get(&instance_id)
}
pub fn get_instance_mut(&mut self, instance_id: InstanceId) -> Option<&mut SandboxInstance> {
self.instances.get_mut(&instance_id)
}
pub fn remove_instance(&mut self, instance_id: InstanceId) -> Option<SandboxInstance> {
self.instances.remove(&instance_id)
}
pub fn instance_ids(&self) -> Vec<InstanceId> {
self.instances.keys().copied().collect()
}
pub fn get_instance_resource_usage(&self, instance_id: InstanceId) -> Result<crate::monitoring::DetailedResourceUsage> {
let instance = self.instances.get(&instance_id).ok_or_else(|| {
SandboxError::NotFound {
resource_type: "instance".to_string(),
identifier: instance_id.to_string(),
}
})?;
Ok(instance.monitor.get_current_usage())
}
pub fn reset_instance(&mut self, instance_id: InstanceId) -> Result<()> {
let instance = self.instances.get_mut(&instance_id).ok_or_else(|| {
SandboxError::NotFound {
resource_type: "instance".to_string(),
identifier: instance_id.to_string(),
}
})?;
instance.monitor = crate::monitoring::ResourceMonitor::new(Some(instance_id));
Ok(())
}
}
pub use communication::{CommunicationChannel, RpcChannel};
pub use runtime::{RuntimeMetrics, WasmInstanceState};
pub use security::{
CpuLimits, EnvironmentCapability, FilesystemCapability,
IoLimits, MemoryLimits, NetworkCapability, ProcessCapability,
RandomCapability, TimeCapability,
};
pub use utils::manifest::SandboxManifest;
#[derive(Debug, Clone)]
pub struct WasmSandboxBuilder {
source_path: Option<String>,
timeout: Option<std::time::Duration>,
memory_limit: Option<usize>,
enable_file_access: Option<bool>,
enable_network: Option<bool>,
config: SandboxConfig,
}
impl WasmSandboxBuilder {
pub fn new() -> Self {
Self {
source_path: None,
timeout: None,
memory_limit: None,
enable_file_access: None,
enable_network: None,
config: SandboxConfig::default(),
}
}
pub fn source<S: Into<String>>(mut self, path: S) -> Self {
self.source_path = Some(path.into());
self
}
pub fn timeout_duration(mut self, duration: std::time::Duration) -> Self {
self.timeout = Some(duration);
self
}
pub fn memory_limit(mut self, bytes: usize) -> Self {
self.memory_limit = Some(bytes);
self
}
pub fn enable_file_access(mut self, enable: bool) -> Self {
self.enable_file_access = Some(enable);
self
}
pub fn enable_network(mut self, enable: bool) -> Self {
self.enable_network = Some(enable);
self
}
pub async fn build(mut self) -> Result<WasmSandbox> {
let wasm_bytes = if let Some(source_path) = &self.source_path {
compile_source_to_wasm(source_path).await?
} else {
return Err(SandboxError::Configuration {
message: "No source file specified".to_string(),
suggestion: Some("Provide a source file path".to_string()),
field: Some("source_file".to_string()),
});
};
if let Some(timeout) = self.timeout {
self.config.default_instance_config.startup_timeout_ms = timeout.as_millis() as u64;
}
if let Some(memory_limit) = self.memory_limit {
self.config.default_instance_config.resource_limits.memory.max_memory_pages = (memory_limit / 65536) as u32;
}
if let Some(enable_files) = self.enable_file_access {
if enable_files {
let current_dir = std::env::current_dir().unwrap_or_default();
self.config.default_instance_config.capabilities.filesystem.readable_dirs.push(current_dir.clone());
self.config.default_instance_config.capabilities.filesystem.writable_dirs.push(current_dir);
self.config.default_instance_config.capabilities.filesystem.allow_create = true;
} else {
self.config.default_instance_config.capabilities.filesystem.readable_dirs.clear();
self.config.default_instance_config.capabilities.filesystem.writable_dirs.clear();
self.config.default_instance_config.capabilities.filesystem.allow_create = false;
}
}
if let Some(enable_net) = self.enable_network {
if enable_net {
self.config.default_instance_config.capabilities.network = crate::security::NetworkCapability::Loopback;
} else {
self.config.default_instance_config.capabilities.network = crate::security::NetworkCapability::None;
}
}
let mut sandbox = WasmSandbox::with_config(self.config)?;
let module_id = sandbox.load_module(&wasm_bytes)?;
let _instance_id = sandbox.create_instance(module_id, None)?;
Ok(sandbox)
}
}
impl Default for WasmSandboxBuilder {
fn default() -> Self {
Self::new()
}
}
pub async fn compile_source_to_wasm(source_path: &str) -> Result<Vec<u8>> {
use std::path::Path;
let path = Path::new(source_path);
let extension = path.extension()
.and_then(|ext| ext.to_str())
.ok_or_else(|| SandboxError::config_error("Could not determine file extension", None))?;
match extension {
"rs" => compile_rust_to_wasm(source_path).await,
"py" => compile_python_to_wasm(source_path).await,
"c" | "cpp" | "cc" => compile_c_to_wasm(source_path).await,
"js" | "ts" => compile_javascript_to_wasm(source_path).await,
"go" => compile_go_to_wasm(source_path).await,
"wasm" => {
std::fs::read(source_path)
.map_err(|e| SandboxError::Filesystem {
operation: "read".to_string(),
path: std::path::PathBuf::from(source_path),
reason: e.to_string()
})
},
_ => Err(SandboxError::Unsupported {
operation: format!("compile source language: {}", extension),
context: "automatic compilation".to_string(),
suggestion: Some("Supported languages: rs, py, c, cpp, js, ts, go, wasm".to_string())
}),
}
}
async fn compile_rust_to_wasm(source_path: &str) -> Result<Vec<u8>> {
use std::path::Path;
use std::process::Command;
let source_path = Path::new(source_path);
if !source_path.exists() {
return Err(SandboxError::NotFound {
resource_type: "source file".to_string(),
identifier: source_path.display().to_string()
});
}
let temp_dir = std::env::temp_dir().join(format!("wasm-sandbox-{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&temp_dir).map_err(|e| SandboxError::Filesystem {
operation: "create_directory".to_string(),
path: temp_dir.clone(),
reason: e.to_string(),
})?;
let cargo_toml = r#"
[package]
name = "wasm-module"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.6"
[dependencies.web-sys]
version = "0.3"
features = [
"console",
]
"#;
std::fs::write(temp_dir.join("Cargo.toml"), cargo_toml)
.map_err(|e| SandboxError::Filesystem {
operation: "write_file".to_string(),
path: temp_dir.join("Cargo.toml"),
reason: e.to_string(),
})?;
let src_dir = temp_dir.join("src");
std::fs::create_dir_all(&src_dir).map_err(|e| SandboxError::Filesystem {
operation: "create_directory".to_string(),
path: src_dir.clone(),
reason: e.to_string(),
})?;
let source_content = std::fs::read_to_string(source_path)
.map_err(|e| SandboxError::Filesystem {
operation: "read_file".to_string(),
path: source_path.to_path_buf(),
reason: e.to_string(),
})?;
let wrapped_source = format!(r#"
use wasm_bindgen::prelude::*;
// Import the `console.log` function from the Web API
#[wasm_bindgen]
extern "C" {{
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}}
// A macro to provide `println!(..)`-style syntax for `console.log` logging
macro_rules! console_log {{
( $( $t:tt )* ) => {{
log(&format!( $( $t )* ))
}}
}}
{source_content}
// Auto-export common function patterns
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {{
a + b
}}
"#);
std::fs::write(src_dir.join("lib.rs"), wrapped_source)
.map_err(|e| SandboxError::Filesystem {
operation: "write_file".to_string(),
path: src_dir.join("lib.rs"),
reason: e.to_string(),
})?;
let output = Command::new("cargo")
.arg("build")
.arg("--target")
.arg("wasm32-unknown-unknown")
.arg("--release")
.current_dir(&temp_dir)
.output()
.map_err(|e| SandboxError::Module {
operation: "compile".to_string(),
reason: format!("Failed to run cargo: {}", e),
suggestion: Some("Ensure cargo is installed and in your PATH".to_string()),
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(SandboxError::Module {
operation: "compile".to_string(),
reason: format!("Cargo build failed: {}", stderr),
suggestion: Some("Check your Rust code for compilation errors".to_string()),
});
}
let wasm_path = temp_dir.join("target/wasm32-unknown-unknown/release/wasm_module.wasm");
let wasm_bytes = std::fs::read(&wasm_path)
.map_err(|e| SandboxError::Filesystem {
operation: "read_file".to_string(),
path: wasm_path,
reason: e.to_string(),
})?;
let _ = std::fs::remove_dir_all(&temp_dir);
Ok(wasm_bytes)
}
async fn compile_python_to_wasm(_source_path: &str) -> Result<Vec<u8>> {
Err(SandboxError::Unsupported {
operation: "Python compilation".to_string(),
context: "language support".to_string(),
suggestion: Some("Use Rust compilation instead".to_string()),
})
}
async fn compile_c_to_wasm(_source_path: &str) -> Result<Vec<u8>> {
Err(SandboxError::Unsupported {
operation: "C/C++ compilation".to_string(),
context: "language support".to_string(),
suggestion: Some("Use Rust compilation instead".to_string()),
})
}
async fn compile_javascript_to_wasm(_source_path: &str) -> Result<Vec<u8>> {
Err(SandboxError::Unsupported {
operation: "JavaScript/TypeScript compilation".to_string(),
context: "language support".to_string(),
suggestion: Some("Use Rust compilation instead".to_string()),
})
}
async fn compile_go_to_wasm(_source_path: &str) -> Result<Vec<u8>> {
Err(SandboxError::Unsupported {
operation: "Go compilation".to_string(),
context: "language support".to_string(),
suggestion: Some("Use Rust compilation instead".to_string()),
})
}
#[cfg(feature = "python-bindings")]
pub mod bindings;
pub mod streaming;
pub use streaming::{StreamingExecution, StreamingExecutor, StreamingConfig, StreamingConfigExt, FunctionCall, FunctionResult};
pub mod plugins;
pub use plugins::{
WasmPlugin, PluginManifest, EntryPoint, ExecutionContext, PluginHealth,
HotReload, CompatibilityReport, PluginValidator, PluginRegistry,
SecurityAuditReport, BenchmarkReport
};
pub mod simple;
pub use simple::{SimpleSandbox, ReusableSandbox, from_source};