Add WASM sandbox secure API extension

Extends the WASM sandbox with HTTP API capabilities, secrets management,
tool aliasing, and leak detection. Key security principle: WASM never
sees credentials, injection happens at host boundary.

New modules:
- secrets: AES-256-GCM encrypted storage with HKDF key derivation
- leak_detector: Aho-Corasick + regex pattern matching for secret exfiltration
- capabilities: Extended capability system (HTTP, ToolInvoke, Secrets)
- allowlist: HTTP endpoint validation with glob patterns
- credential_injector: Host-boundary credential injection
- rate_limiter: Sliding window per-tool rate limiting
- storage: WASM binary storage with BLAKE3 integrity verification

Leak detection happens at two points:
1. Before HTTP request (prevents exfiltration via URL/headers/body)
2. After response (prevents exposure in outputs returned to WASM)

Co-Authored-By: Claude Opus 4.5 <[email protected]>
This commit is contained in:
Illia Polosukhin
2026-02-02 23:22:52 -08:00
co-authored by Claude Opus 4.5
parent 45bbfa026d
commit 32bfd24154
21 changed files with 5115 additions and 66 deletions
+50 -20
View File
@@ -10,26 +10,23 @@
//!
//! - **Memory limits**: Memory growth is bounded via ResourceLimiter.
//!
//! - **Minimal host API**: Only log, time, and optional workspace read.
//! - **Extended host API (V2)**: log, time, workspace, HTTP, tool invoke, secrets
//!
//! - **Capability-based security**: Features are opt-in via Capabilities.
//!
//! # Architecture
//! # Architecture (V2)
//!
//! ```text
//! ┌─────────────────────────────────────────────────────────────────────┐
//! │ Tool Registration │
//! │ WASM bytes → Validate → Compile (AOT) → PreparedModule (cached)
//! └─────────────────────────────────────────────────────────────────────┘
//!
//!
//! ┌─────────────────────────────────────────────────────────────────────┐
//! │ Tool Execution
//! │ JSON params → WasmToolWrapper → Fresh Instance → Execute → Result
//! │ ↓ ↓ │
//! │ ResourceLimiter HostState │
//! │ (memory, fuel) (log, time, workspace) │
//! └─────────────────────────────────────────────────────────────────────┘
//! ┌─────────────────────────────────────────────────────────────────────────────
//! │ WASM Tool Execution
//! │
//! │ WASM Tool ──▶ Host Function ──▶ Allowlist ──▶ Credential ──▶ Execute │
//! (untrusted) (boundary) Validator Injector Request
//! │ │
//! │ ▼ │
//! │ ◀────── Leak Detector ◀────── Response
//! │ (sanitized, no secrets)
//! └─────────────────────────────────────────────────────────────────────────────┘
//! ```
//!
//! # Security Constraints
@@ -40,17 +37,22 @@
//! | Memory exhaustion | ResourceLimiter, 10MB default |
//! | Infinite loops | Epoch interruption + tokio timeout |
//! | Filesystem access | No WASI FS, only host workspace_read |
//! | Network access | No network host functions |
//! | 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 |
//! | Direct tool access | Tool aliasing (indirection layer) |
//!
//! # Example
//!
//! ```ignore
//! use near_agent::tools::wasm::{WasmToolRuntime, WasmRuntimeConfig, WasmToolWrapper};
//! use near_agent::tools::wasm::host::Capabilities;
//! use near_agent::tools::wasm::Capabilities;
//! use std::sync::Arc;
//!
//! // Create runtime
@@ -60,24 +62,52 @@
//! let wasm_bytes = std::fs::read("my_tool.wasm")?;
//! let prepared = runtime.prepare("my_tool", &wasm_bytes, None).await?;
//!
//! // Create wrapper with minimal capabilities
//! let tool = WasmToolWrapper::new(runtime, prepared, Capabilities::default());
//! // Create wrapper with HTTP capability
//! let capabilities = Capabilities::none()
//! .with_http(HttpCapability::new(vec![
//! EndpointPattern::host("api.openai.com").with_path_prefix("/v1/"),
//! ]));
//! let tool = WasmToolWrapper::new(runtime, prepared, capabilities);
//!
//! // Execute (implements Tool trait)
//! let output = tool.execute(serde_json::json!({"input": "test"}), &ctx).await?;
//! ```
mod allowlist;
mod capabilities;
mod credential_injector;
mod error;
mod host;
mod limits;
mod rate_limiter;
mod runtime;
mod storage;
mod wrapper;
// Core types
pub use error::{TrapCode, TrapInfo, WasmError};
pub use host::{Capabilities, HostState, LogEntry, LogLevel, WorkspaceCapability, WorkspaceReader};
pub use host::{HostState, LogEntry, LogLevel};
pub use limits::{
DEFAULT_FUEL_LIMIT, DEFAULT_MEMORY_LIMIT, DEFAULT_TIMEOUT, FuelConfig, ResourceLimits,
WasmResourceLimiter,
};
pub use runtime::{PreparedModule, WasmRuntimeConfig, WasmToolRuntime};
pub use wrapper::WasmToolWrapper;
// Capabilities (V2)
pub use capabilities::{
Capabilities, EndpointPattern, HttpCapability, RateLimitConfig, SecretsCapability,
ToolInvokeCapability, WorkspaceCapability, WorkspaceReader,
};
// Security components (V2)
pub use allowlist::{AllowlistResult, AllowlistValidator, DenyReason};
pub use credential_injector::{CredentialInjector, InjectedCredentials, InjectionError};
pub use rate_limiter::{LimitType, RateLimitError, RateLimitResult, RateLimiter};
// Storage (V2)
pub use storage::{
PostgresWasmToolStore, StoreToolParams, StoredCapabilities, StoredWasmTool,
StoredWasmToolWithBinary, ToolStatus, TrustLevel, WasmStorageError, WasmToolStore,
compute_binary_hash, verify_binary_integrity,
};