use serde_json::json;
use tokio::time::{Duration, timeout};
use super::*;
use crate::types::{ErrorType, ExecutionContext, ExecutionResult, SecurityViolationType};
fn create_test_context() -> ExecutionContext {
ExecutionContext::new("test".to_string(), json!({}))
}
#[allow(unsafe_code)]
fn set_once() {
static ONCE: std::sync::Once = std::sync::Once::new();
ONCE.call_once(|| {
unsafe {
std::env::set_var("MCP_USE_WASM", "false");
}
});
}
#[tokio::test]
async fn test_simple_execution() {
set_once();
if std::env::var("CI").is_ok() {
return;
}
let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
let code = "return 1 + 1;";
let context = create_test_context();
let result = sandbox.execute(code, context).await.unwrap();
match result {
ExecutionResult::Success { .. } => {
}
other => panic!("Expected success, got: {:?}", other),
}
}
#[tokio::test]
async fn test_console_output() {
set_once();
if std::env::var("CI").is_ok() {
return;
}
let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
let code = r#"
console.log("Hello");
console.log("World");
return "done";
"#;
let context = create_test_context();
let result = sandbox.execute(code, context).await.unwrap();
match result {
ExecutionResult::Success { stdout, .. } => {
assert!(stdout.contains("Hello"));
assert!(stdout.contains("World"));
}
other => panic!("Expected success, got: {:?}", other),
}
}
#[tokio::test]
async fn test_timeout_enforcement() {
timeout(Duration::from_secs(10), async {
set_once();
let config = SandboxConfig {
max_execution_time_ms: 500, ..Default::default()
};
let sandbox = CodeSandbox::new(config).unwrap();
let code = r#"
let sum = 0;
for (let i = 0; i !== 10000000000; i++) {
sum += i;
if (i % 1000000 === 0) {
const x = sum;
}
}
return sum;
"#;
let context = create_test_context();
let result = sandbox.execute(code, context).await.unwrap();
match result {
ExecutionResult::Timeout { .. } => {
}
other => panic!("Expected timeout, got: {:?}", other),
}
})
.await
.expect("Test timed out after 10 seconds");
}
#[tokio::test]
async fn test_filesystem_blocking() {
timeout(Duration::from_secs(10), async {
set_once();
let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
let code = r#"
const fs = require('fs');
fs.readFileSync('/etc/passwd');
"#;
let context = create_test_context();
let result = sandbox.execute(code, context).await.unwrap();
match result {
ExecutionResult::SecurityViolation {
violation_type: SecurityViolationType::FileSystemAccess,
..
} => {
}
other => panic!("Expected security violation, got: {:?}", other),
}
})
.await
.expect("Test timed out after 10 seconds");
}
#[tokio::test]
async fn test_network_blocking() {
timeout(Duration::from_secs(10), async {
set_once();
let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
let code = r#"
const https = require('https');
https.get('https://example.com');
"#;
let context = create_test_context();
let result = sandbox.execute(code, context).await.unwrap();
match result {
ExecutionResult::SecurityViolation {
violation_type: SecurityViolationType::NetworkAccess,
..
} => {
}
other => panic!("Expected security violation, got: {:?}", other),
}
})
.await
.expect("Test timed out after 10 seconds");
}
#[tokio::test]
async fn test_process_execution_blocking() {
set_once();
let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
let code = r#"
const { exec } = require('child_process');
exec('ls -la');
"#;
let context = create_test_context();
let result = sandbox.execute(code, context).await.unwrap();
match result {
ExecutionResult::SecurityViolation {
violation_type: SecurityViolationType::ProcessExecution,
..
} => {
}
other => panic!("Expected security violation, got: {:?}", other),
}
}
#[tokio::test]
async fn test_security_bypass_attempts() {
set_once();
let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
let context = create_test_context();
let code1 = "const fs = require ( 'fs' );";
let result1 = sandbox.execute(code1, context.clone()).await.unwrap();
assert!(matches!(
result1,
ExecutionResult::SecurityViolation {
violation_type: SecurityViolationType::FileSystemAccess,
..
}
));
let code2 = "const fs = require(`fs`);";
let result2 = sandbox.execute(code2, context.clone()).await.unwrap();
assert!(matches!(
result2,
ExecutionResult::SecurityViolation {
violation_type: SecurityViolationType::FileSystemAccess,
..
}
));
let code3 = "const fs = require(\n'fs'\n);";
let result3 = sandbox.execute(code3, context.clone()).await.unwrap();
assert!(matches!(
result3,
ExecutionResult::SecurityViolation {
violation_type: SecurityViolationType::FileSystemAccess,
..
}
));
let code4 = "const f = new Function('return process');";
let result4 = sandbox.execute(code4, context.clone()).await.unwrap();
assert!(matches!(
result4,
ExecutionResult::SecurityViolation {
violation_type: SecurityViolationType::MaliciousCode,
..
}
));
let code5 = "const r = 'req' + 'uire'; const fs = global[r]('fs');";
let result5 = sandbox.execute(code5, context.clone()).await.unwrap();
match result5 {
ExecutionResult::Error { .. } => {
}
other => panic!(
"Expected runtime error for split-string bypass, got: {:?}",
other
),
}
}
#[tokio::test]
async fn test_infinite_loop_detection() {
timeout(Duration::from_secs(10), async {
set_once();
let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
let code = "while(true) {}";
let context = create_test_context();
let result = sandbox.execute(code, context).await.unwrap();
match result {
ExecutionResult::SecurityViolation {
violation_type: SecurityViolationType::InfiniteLoop,
..
} => {
}
other => panic!("Expected security violation, got: {:?}", other),
}
})
.await
.expect("Test timed out after 10 seconds");
}
#[tokio::test]
async fn test_eval_blocking() {
set_once();
let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
let code = r#"eval("malicious code");"#;
let context = create_test_context();
let result = sandbox.execute(code, context).await.unwrap();
match result {
ExecutionResult::SecurityViolation {
violation_type: SecurityViolationType::MaliciousCode,
..
} => {
}
other => panic!("Expected security violation, got: {:?}", other),
}
}
#[tokio::test]
async fn test_syntax_error() {
set_once();
if std::env::var("CI").is_ok() {
return;
}
let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
let code = "const x = ;"; let context = create_test_context();
let result = sandbox.execute(code, context).await.unwrap();
match &result {
ExecutionResult::Error {
error_type: ErrorType::Syntax,
..
}
| ExecutionResult::Error {
error_type: ErrorType::Runtime,
..
} => {
}
other => panic!("Expected syntax/runtime error, got: {:?}", other),
}
}
#[tokio::test]
async fn test_runtime_error() {
set_once();
if std::env::var("CI").is_ok() {
return;
}
let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
let code = r#"
throw new Error("Runtime error");
"#;
let context = create_test_context();
let result = sandbox.execute(code, context).await.unwrap();
match result {
ExecutionResult::Error {
error_type: ErrorType::Runtime,
message,
..
} => {
assert!(message.contains("Runtime error"));
}
other => panic!("Expected runtime error, got: {:?}", other),
}
}
#[test]
fn test_wrapper_deletes_dangerous_globals() {
set_once();
let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
let wrapper = sandbox
.create_secure_wrapper("1+1", &create_test_context())
.unwrap();
assert!(
wrapper.contains("delete global.process"),
"Wrapper must delete global.process"
);
assert!(
wrapper.contains("delete global.require"),
"Wrapper must delete global.require"
);
assert!(
wrapper.contains("delete global.eval"),
"Wrapper must delete global.eval to prevent runtime obfuscation bypass"
);
assert!(
wrapper.contains("delete global.Function"),
"Wrapper must delete global.Function to prevent runtime obfuscation bypass"
);
}
#[tokio::test]
async fn test_preflight_node_check() {
set_once();
if which::which("node").is_ok() {
let result = CodeSandbox::new(SandboxConfig::default());
assert!(
result.is_ok(),
"CodeSandbox::new should succeed when node is available"
);
} else {
let result = CodeSandbox::new(SandboxConfig::default());
assert!(
result.is_err(),
"CodeSandbox::new should error when node is not in PATH"
);
}
}
#[tokio::test]
async fn test_eval_runtime_blocking() {
timeout(Duration::from_secs(10), async {
set_once();
if std::env::var("CI").is_ok() {
return;
}
let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
let context = create_test_context();
let code = "const e = 'ev' + 'al'; global[e]('process');";
let result = sandbox.execute(code, context.clone()).await.unwrap();
match result {
ExecutionResult::Error { message, .. } => {
assert!(
message.contains("is not a function")
|| message.contains("is not defined")
|| message.contains("cannot read")
);
}
other => panic!("Expected runtime error for eval bypass, got: {:?}", other),
}
let code2 = "const f = global['Fun' + 'ction']('return process');";
let result2 = sandbox.execute(code2, context).await.unwrap();
match result2 {
ExecutionResult::Error { message, .. } => {
assert!(
message.contains("is not a function")
|| message.contains("is not defined")
|| message.contains("cannot read")
);
}
other => panic!(
"Expected runtime error for Function bypass, got: {:?}",
other
),
}
})
.await
.expect("Test timed out after 10 seconds");
}
#[tokio::test]
async fn test_code_length_limit() {
set_once();
let sandbox = CodeSandbox::new(SandboxConfig::default()).unwrap();
let code = "a".repeat(100_001); let context = create_test_context();
let result = sandbox.execute(&code, context).await.unwrap();
match result {
ExecutionResult::SecurityViolation { .. } => {
}
other => panic!("Expected security violation, got: {:?}", other),
}
}