Files
optimclaw/src/tools/wasm/limits.rs
T
Illia PolosukhinandClaude Opus 4.5 7955c9742e Add Telegram webhook support with credential injection
Enable instant message delivery for Telegram via webhooks instead of polling.

Key changes:
- Add tunnel URL configuration for local development (ngrok, cloudflare)
- Auto-register webhook with Telegram API on startup using setWebhook
- Implement webhook secret validation via X-Telegram-Bot-Api-Secret-Token header
- Add credential injection for bot token via URL placeholder substitution
- Fix metadata preservation in respond() to route replies correctly
- Fix serde flatten with Option<T> issue in capabilities schema parsing

The credential injection pattern replaces {TELEGRAM_BOT_TOKEN} placeholders
in URLs with the actual token from the secrets store, keeping credentials
out of WASM module memory until the HTTP request is made.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
2026-02-04 21:19:42 -08:00

265 lines
7.1 KiB
Rust

//! Resource limits for WASM sandbox execution.
//!
//! Provides memory and fuel (CPU) limits following NEAR blockchain patterns.
use std::time::Duration;
use wasmtime::ResourceLimiter;
/// Default memory limit: 10 MB (conservative for untrusted code).
pub const DEFAULT_MEMORY_LIMIT: u64 = 10 * 1024 * 1024;
/// Default fuel limit: 10 million instructions.
pub const DEFAULT_FUEL_LIMIT: u64 = 10_000_000;
/// Default execution timeout: 60 seconds.
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60);
/// Resource limits for a single WASM execution.
#[derive(Debug, Clone)]
pub struct ResourceLimits {
/// Maximum memory in bytes.
pub memory_bytes: u64,
/// Maximum fuel (instruction count).
pub fuel: u64,
/// Maximum wall-clock execution time.
pub timeout: Duration,
}
impl Default for ResourceLimits {
fn default() -> Self {
Self {
memory_bytes: DEFAULT_MEMORY_LIMIT,
fuel: DEFAULT_FUEL_LIMIT,
timeout: DEFAULT_TIMEOUT,
}
}
}
impl ResourceLimits {
/// Create limits with custom memory.
pub fn with_memory(mut self, bytes: u64) -> Self {
self.memory_bytes = bytes;
self
}
/// Create limits with custom fuel.
pub fn with_fuel(mut self, fuel: u64) -> Self {
self.fuel = fuel;
self
}
/// Create limits with custom timeout.
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
}
/// Wasmtime ResourceLimiter implementation for enforcing memory limits.
///
/// This is attached to the Store to limit memory growth during execution.
#[derive(Debug)]
pub struct WasmResourceLimiter {
/// Maximum memory allowed.
memory_limit: u64,
/// Current memory usage (tracked across all memories).
memory_used: u64,
/// Maximum tables allowed.
max_tables: u32,
/// Current table count.
#[allow(dead_code)] // Reserved for table limit enforcement
tables_created: u32,
/// Maximum instances allowed.
max_instances: u32,
/// Current instance count.
#[allow(dead_code)] // Reserved for instance limit enforcement
instances_created: u32,
}
impl WasmResourceLimiter {
/// Create a new limiter with the given memory limit.
///
/// Note: max_instances is set to 10 to accommodate WASM Component Model
/// which creates multiple internal instances (main component + WASI adapters).
pub fn new(memory_limit: u64) -> Self {
Self {
memory_limit,
memory_used: 0,
max_tables: 10,
tables_created: 0,
max_instances: 10, // Component model needs multiple instances for WASI
instances_created: 0,
}
}
/// Get current memory usage.
pub fn memory_used(&self) -> u64 {
self.memory_used
}
/// Get the memory limit.
pub fn memory_limit(&self) -> u64 {
self.memory_limit
}
}
impl ResourceLimiter for WasmResourceLimiter {
fn memory_growing(
&mut self,
current: usize,
desired: usize,
_maximum: Option<usize>,
) -> anyhow::Result<bool> {
let desired_u64 = desired as u64;
if desired_u64 > self.memory_limit {
tracing::warn!(
current = current,
desired = desired,
limit = self.memory_limit,
"WASM memory growth denied: would exceed limit"
);
return Ok(false);
}
self.memory_used = desired_u64;
tracing::trace!(
current = current,
desired = desired,
limit = self.memory_limit,
"WASM memory growth allowed"
);
Ok(true)
}
fn table_growing(
&mut self,
current: usize,
desired: usize,
_maximum: Option<usize>,
) -> anyhow::Result<bool> {
// Allow reasonable table growth
if desired > 10_000 {
tracing::warn!(
current = current,
desired = desired,
"WASM table growth denied: too large"
);
return Ok(false);
}
Ok(true)
}
fn instances(&self) -> usize {
self.max_instances as usize
}
fn tables(&self) -> usize {
self.max_tables as usize
}
fn memories(&self) -> usize {
// Allow multiple memories for component model with WASI
self.max_instances as usize
}
}
/// Configuration for fuel metering.
#[derive(Debug, Clone)]
pub struct FuelConfig {
/// Initial fuel to provide.
pub initial_fuel: u64,
/// Whether to enable fuel consumption.
pub enabled: bool,
}
impl Default for FuelConfig {
fn default() -> Self {
Self {
initial_fuel: DEFAULT_FUEL_LIMIT,
enabled: true,
}
}
}
impl FuelConfig {
/// Create a disabled fuel config (no CPU limits).
pub fn disabled() -> Self {
Self {
initial_fuel: 0,
enabled: false,
}
}
/// Create a fuel config with a custom limit.
pub fn with_limit(fuel: u64) -> Self {
Self {
initial_fuel: fuel,
enabled: true,
}
}
}
#[cfg(test)]
mod tests {
use crate::tools::wasm::limits::{
DEFAULT_FUEL_LIMIT, DEFAULT_MEMORY_LIMIT, DEFAULT_TIMEOUT, FuelConfig, ResourceLimits,
WasmResourceLimiter,
};
use wasmtime::ResourceLimiter;
#[test]
fn test_default_limits() {
let limits = ResourceLimits::default();
assert_eq!(limits.memory_bytes, DEFAULT_MEMORY_LIMIT);
assert_eq!(limits.fuel, DEFAULT_FUEL_LIMIT);
assert_eq!(limits.timeout, DEFAULT_TIMEOUT);
}
#[test]
fn test_limits_builder() {
let limits = ResourceLimits::default()
.with_memory(5 * 1024 * 1024)
.with_fuel(1_000_000)
.with_timeout(std::time::Duration::from_secs(30));
assert_eq!(limits.memory_bytes, 5 * 1024 * 1024);
assert_eq!(limits.fuel, 1_000_000);
assert_eq!(limits.timeout, std::time::Duration::from_secs(30));
}
#[test]
fn test_resource_limiter_allows_growth_within_limit() {
let mut limiter = WasmResourceLimiter::new(10 * 1024 * 1024);
// Growth within limit should be allowed
let result = limiter.memory_growing(0, 1024 * 1024, None).unwrap();
assert!(result);
assert_eq!(limiter.memory_used(), 1024 * 1024);
}
#[test]
fn test_resource_limiter_denies_growth_beyond_limit() {
let mut limiter = WasmResourceLimiter::new(10 * 1024 * 1024);
// Growth beyond limit should be denied
let result = limiter.memory_growing(0, 20 * 1024 * 1024, None).unwrap();
assert!(!result);
}
#[test]
fn test_fuel_config() {
let config = FuelConfig::default();
assert!(config.enabled);
assert_eq!(config.initial_fuel, DEFAULT_FUEL_LIMIT);
let disabled = FuelConfig::disabled();
assert!(!disabled.enabled);
let custom = FuelConfig::with_limit(5_000_000);
assert!(custom.enabled);
assert_eq!(custom.initial_fuel, 5_000_000);
}
}