Files
claudia/src-tauri/tests/sandbox/integration/network_operations.rs
2025-06-19 19:24:01 +05:30

301 lines
10 KiB
Rust

//! Integration tests for network operations in sandbox
use crate::sandbox::common::*;
use crate::skip_if_unsupported;
use claudia_lib::sandbox::executor::SandboxExecutor;
use gaol::profile::{Profile, Operation, AddressPattern};
use serial_test::serial;
use std::net::TcpListener;
use tempfile::TempDir;
/// Get an available port for testing
fn get_available_port() -> u16 {
let listener = TcpListener::bind("127.0.0.1:0").expect("Failed to bind to 0");
let port = listener.local_addr().expect("Failed to get local addr").port();
drop(listener); // Release the port
port
}
/// Test allowed network operations
#[test]
#[serial]
fn test_allowed_network_all() {
skip_if_unsupported!();
let platform = PlatformConfig::current();
if !platform.supports_network_all {
eprintln!("Skipping test: network all not supported on this platform");
return;
}
// Create test project
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
// Create profile allowing all network access
let operations = vec![
Operation::NetworkOutbound(AddressPattern::All),
];
let profile = match Profile::new(operations) {
Ok(p) => p,
Err(_) => {
eprintln!("Failed to create profile - operation not supported");
return;
}
};
// Create test binary that connects to localhost
let port = get_available_port();
let test_code = test_code::network_connect(&format!("127.0.0.1:{}", port));
let binary_dir = TempDir::new().expect("Failed to create temp dir");
let binary_path = create_test_binary("test_network", &test_code, binary_dir.path())
.expect("Failed to create test binary");
// Start a listener on the port
let listener = TcpListener::bind(format!("127.0.0.1:{}", port))
.expect("Failed to bind listener");
// Execute in sandbox
let executor = SandboxExecutor::new(profile, test_fs.project_path.clone());
match executor.execute_sandboxed_spawn(
&binary_path.to_string_lossy(),
&[],
&test_fs.project_path,
) {
Ok(mut child) => {
// Accept connection in a thread
std::thread::spawn(move || {
let _ = listener.accept();
});
let status = child.wait().expect("Failed to wait for child");
assert!(status.success(), "Network connection should succeed when allowed");
}
Err(e) => {
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
}
}
}
/// Test forbidden network operations
#[test]
#[serial]
fn test_forbidden_network() {
skip_if_unsupported!();
// Create test project
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
// Create profile without network permissions
let operations = vec![
Operation::FileReadAll(gaol::profile::PathPattern::Subpath(test_fs.project_path.clone())),
];
let profile = match Profile::new(operations) {
Ok(p) => p,
Err(_) => {
eprintln!("Failed to create profile - operation not supported");
return;
}
};
// Create test binary that tries to connect
let test_code = test_code::network_connect("google.com:80");
let binary_dir = TempDir::new().expect("Failed to create temp dir");
let binary_path = create_test_binary("test_no_network", &test_code, binary_dir.path())
.expect("Failed to create test binary");
// Execute in sandbox
let executor = SandboxExecutor::new(profile, test_fs.project_path.clone());
match executor.execute_sandboxed_spawn(
&binary_path.to_string_lossy(),
&[],
&test_fs.project_path,
) {
Ok(mut child) => {
let status = child.wait().expect("Failed to wait for child");
// Network restrictions might not work on all platforms
if status.success() {
eprintln!("WARNING: Network connection was not blocked (platform limitation)");
if std::env::consts::OS == "linux" {
panic!("Network should be blocked on Linux when not allowed");
}
}
}
Err(e) => {
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
}
}
}
/// Test TCP port-specific network rules (macOS only)
#[test]
#[serial]
#[cfg(target_os = "macos")]
fn test_network_tcp_port_specific() {
let platform = PlatformConfig::current();
if !platform.supports_network_tcp {
eprintln!("Skipping test: TCP port filtering not supported");
return;
}
// Create test project
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
// Get two ports - one allowed, one forbidden
let allowed_port = get_available_port();
let forbidden_port = get_available_port();
// Create profile allowing only specific port
let operations = vec![
Operation::NetworkOutbound(AddressPattern::Tcp(allowed_port)),
];
let profile = match Profile::new(operations) {
Ok(p) => p,
Err(_) => {
eprintln!("Failed to create profile - operation not supported");
return;
}
};
// Test 1: Allowed port
{
let test_code = test_code::network_connect(&format!("127.0.0.1:{}", allowed_port));
let binary_dir = TempDir::new().expect("Failed to create temp dir");
let binary_path = create_test_binary("test_allowed_port", &test_code, binary_dir.path())
.expect("Failed to create test binary");
let listener = TcpListener::bind(format!("127.0.0.1:{}", allowed_port))
.expect("Failed to bind listener");
let executor = SandboxExecutor::new(profile.clone(), test_fs.project_path.clone());
match executor.execute_sandboxed_spawn(
&binary_path.to_string_lossy(),
&[],
&test_fs.project_path,
) {
Ok(mut child) => {
std::thread::spawn(move || {
let _ = listener.accept();
});
let status = child.wait().expect("Failed to wait for child");
assert!(status.success(), "Connection to allowed port should succeed");
}
Err(e) => {
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
}
}
}
// Test 2: Forbidden port
{
let test_code = test_code::network_connect(&format!("127.0.0.1:{}", forbidden_port));
let binary_dir = TempDir::new().expect("Failed to create temp dir");
let binary_path = create_test_binary("test_forbidden_port", &test_code, binary_dir.path())
.expect("Failed to create test binary");
let executor = SandboxExecutor::new(profile, test_fs.project_path.clone());
match executor.execute_sandboxed_spawn(
&binary_path.to_string_lossy(),
&[],
&test_fs.project_path,
) {
Ok(mut child) => {
let status = child.wait().expect("Failed to wait for child");
assert!(!status.success(), "Connection to forbidden port should fail");
}
Err(e) => {
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
}
}
}
}
/// Test local socket connections (Unix domain sockets)
#[test]
#[serial]
#[cfg(unix)]
fn test_local_socket_connections() {
skip_if_unsupported!();
let platform = PlatformConfig::current();
// Create test project
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
let socket_path = test_fs.project_path.join("test.sock");
// Create appropriate profile based on platform
let operations = if platform.supports_network_local {
vec![
Operation::NetworkOutbound(AddressPattern::LocalSocket(socket_path.clone())),
]
} else if platform.supports_network_all {
// Fallback to allowing all network
vec![
Operation::NetworkOutbound(AddressPattern::All),
]
} else {
eprintln!("Skipping test: no network support on this platform");
return;
};
let profile = match Profile::new(operations) {
Ok(p) => p,
Err(_) => {
eprintln!("Failed to create profile - operation not supported");
return;
}
};
// Create test binary that connects to local socket
let test_code = format!(
r#"
use std::os::unix::net::UnixStream;
fn main() {{
match UnixStream::connect("{}") {{
Ok(_) => {{
println!("SUCCESS: Connected to local socket");
}}
Err(e) => {{
eprintln!("FAILURE: {{}}", e);
std::process::exit(1);
}}
}}
}}
"#,
socket_path.to_string_lossy()
);
let binary_dir = TempDir::new().expect("Failed to create temp dir");
let binary_path = create_test_binary("test_local_socket", &test_code, binary_dir.path())
.expect("Failed to create test binary");
// Create Unix socket listener
use std::os::unix::net::UnixListener;
let listener = UnixListener::bind(&socket_path).expect("Failed to bind Unix socket");
// Execute in sandbox
let executor = SandboxExecutor::new(profile, test_fs.project_path.clone());
match executor.execute_sandboxed_spawn(
&binary_path.to_string_lossy(),
&[],
&test_fs.project_path,
) {
Ok(mut child) => {
std::thread::spawn(move || {
let _ = listener.accept();
});
let status = child.wait().expect("Failed to wait for child");
assert!(status.success(), "Local socket connection should succeed when allowed");
}
Err(e) => {
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
}
}
// Clean up socket file
let _ = std::fs::remove_file(&socket_path);
}