153 lines
6.2 KiB
Rust
153 lines
6.2 KiB
Rust
#![deny(warnings)]
|
|
#![allow(dead_code)]
|
|
//! [TSM.ID].[11031972] -- Platform X Ecosystem
|
|
//! xcu-v8-sandbox -- JavaScript Execution Sandbox with Permission System
|
|
use std::collections::HashMap;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
#[derive(Debug)]
|
|
pub enum SandboxError { PermissionDenied(String), MemoryExceeded(String), TimeoutError(String), ScriptError(String) }
|
|
impl std::fmt::Display for SandboxError {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self { Self::PermissionDenied(e) => write!(f, "Permission: {e}"), Self::MemoryExceeded(e) => write!(f, "Memory: {e}"), Self::TimeoutError(e) => write!(f, "Timeout: {e}"), Self::ScriptError(e) => write!(f, "Script: {e}") }
|
|
}
|
|
}
|
|
impl std::error::Error for SandboxError {}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct SandboxPolicy {
|
|
pub max_memory_mb: u64,
|
|
pub max_execution_ms: u64,
|
|
pub allowed_apis: Vec<String>,
|
|
pub denied_apis: Vec<String>,
|
|
pub allow_network: bool,
|
|
pub allow_fs: bool,
|
|
pub allow_eval: bool,
|
|
}
|
|
impl Default for SandboxPolicy {
|
|
fn default() -> Self {
|
|
Self { max_memory_mb: 128, max_execution_ms: 5000,
|
|
allowed_apis: vec!["console".into(), "Math".into(), "JSON".into(), "Date".into(), "Array".into(), "Object".into(), "String".into()],
|
|
denied_apis: vec!["eval".into(), "Function".into(), "require".into(), "import".into(), "process".into(), "fs".into()],
|
|
allow_network: false, allow_fs: false, allow_eval: false }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum JsValue { Undefined, Null, Bool(bool), Number(f64), Str(String), Array(Vec<JsValue>), Object(HashMap<String, JsValue>) }
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ScriptResult { pub value: JsValue, pub memory_used_bytes: u64, pub execution_ms: u64, pub api_calls: Vec<String> }
|
|
|
|
pub struct JsSandbox {
|
|
policy: SandboxPolicy,
|
|
globals: HashMap<String, JsValue>,
|
|
audit_log: Arc<Mutex<Vec<String>>>,
|
|
}
|
|
|
|
impl JsSandbox {
|
|
pub fn new(policy: SandboxPolicy) -> Self {
|
|
Self { policy, globals: HashMap::new(), audit_log: Arc::new(Mutex::new(Vec::new())) }
|
|
}
|
|
|
|
pub fn set_global(&mut self, name: &str, value: JsValue) { self.globals.insert(name.into(), value); }
|
|
|
|
/// Check if API call is permitted
|
|
pub fn check_api(&self, api_name: &str) -> Result<(), SandboxError> {
|
|
if self.policy.denied_apis.iter().any(|d| api_name.starts_with(d)) {
|
|
return Err(SandboxError::PermissionDenied(format!("API '{api_name}' is denied")));
|
|
}
|
|
if !api_name.contains('.') && !self.policy.allowed_apis.iter().any(|a| api_name.starts_with(a)) {
|
|
return Err(SandboxError::PermissionDenied(format!("API '{api_name}' not in allow list")));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Evaluate simple expression (interpreter for basic math/string ops)
|
|
pub fn eval_expr(&mut self, expr: &str) -> Result<ScriptResult, SandboxError> {
|
|
if !self.policy.allow_eval && expr.contains("eval(") {
|
|
return Err(SandboxError::PermissionDenied("eval() is disabled".into()));
|
|
}
|
|
let trimmed = expr.trim();
|
|
let mut api_calls = Vec::new();
|
|
|
|
// Check for denied APIs in expression
|
|
for denied in &self.policy.denied_apis {
|
|
if trimmed.contains(denied.as_str()) {
|
|
return Err(SandboxError::PermissionDenied(format!("'{denied}' found in expression")));
|
|
}
|
|
}
|
|
|
|
// Simple expression evaluator
|
|
let value = if let Ok(n) = trimmed.parse::<f64>() {
|
|
JsValue::Number(n)
|
|
} else if trimmed.starts_with('"') && trimmed.ends_with('"') {
|
|
JsValue::Str(trimmed[1..trimmed.len()-1].to_string())
|
|
} else if trimmed == "true" { JsValue::Bool(true) }
|
|
else if trimmed == "false" { JsValue::Bool(false) }
|
|
else if trimmed == "null" { JsValue::Null }
|
|
else if trimmed.starts_with("Math.") {
|
|
api_calls.push("Math".into());
|
|
match trimmed {
|
|
"Math.PI" => JsValue::Number(std::f64::consts::PI),
|
|
"Math.E" => JsValue::Number(std::f64::consts::E),
|
|
"Math.SQRT2" => JsValue::Number(std::f64::consts::SQRT_2),
|
|
_ => JsValue::Undefined,
|
|
}
|
|
} else if let Some(var) = self.globals.get(trimmed) { var.clone() }
|
|
else { JsValue::Undefined };
|
|
|
|
// Log
|
|
if let Ok(mut log) = self.audit_log.lock() { log.push(format!("eval: {trimmed}")); }
|
|
|
|
Ok(ScriptResult { value, memory_used_bytes: trimmed.len() as u64, execution_ms: 0, api_calls })
|
|
}
|
|
|
|
/// Check memory budget
|
|
pub fn check_memory(&self, used_bytes: u64) -> Result<(), SandboxError> {
|
|
let limit = self.policy.max_memory_mb * 1024 * 1024;
|
|
if used_bytes > limit { Err(SandboxError::MemoryExceeded(format!("{}B > {}B", used_bytes, limit))) }
|
|
else { Ok(()) }
|
|
}
|
|
|
|
pub fn audit_log(&self) -> Vec<String> { self.audit_log.lock().map(|l| l.clone()).unwrap_or_default() }
|
|
pub fn policy(&self) -> &SandboxPolicy { &self.policy }
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
#[test]
|
|
fn test_api_check() {
|
|
let sb = JsSandbox::new(SandboxPolicy::default());
|
|
assert!(sb.check_api("Math").is_ok());
|
|
assert!(sb.check_api("console").is_ok());
|
|
assert!(sb.check_api("eval").is_err());
|
|
assert!(sb.check_api("require").is_err());
|
|
}
|
|
#[test]
|
|
fn test_eval_number() {
|
|
let mut sb = JsSandbox::new(SandboxPolicy::default());
|
|
let r = sb.eval_expr("42").unwrap();
|
|
if let JsValue::Number(n) = r.value { assert_eq!(n, 42.0); }
|
|
}
|
|
#[test]
|
|
fn test_eval_denied() {
|
|
let mut sb = JsSandbox::new(SandboxPolicy::default());
|
|
assert!(sb.eval_expr("require('fs')").is_err());
|
|
}
|
|
#[test]
|
|
fn test_eval_math() {
|
|
let mut sb = JsSandbox::new(SandboxPolicy::default());
|
|
let r = sb.eval_expr("Math.PI").unwrap();
|
|
if let JsValue::Number(n) = r.value { assert!((n - std::f64::consts::PI).abs() < 0.001); }
|
|
}
|
|
#[test]
|
|
fn test_globals() {
|
|
let mut sb = JsSandbox::new(SandboxPolicy::default());
|
|
sb.set_global("x", JsValue::Number(99.0));
|
|
let r = sb.eval_expr("x").unwrap();
|
|
if let JsValue::Number(n) = r.value { assert_eq!(n, 99.0); }
|
|
}
|
|
}
|