init: push source
This commit is contained in:
297
src-tauri/tests/sandbox/integration/file_operations.rs
Normal file
297
src-tauri/tests/sandbox/integration/file_operations.rs
Normal file
@@ -0,0 +1,297 @@
|
||||
//! Integration tests for file operations in sandbox
|
||||
use crate::sandbox::common::*;
|
||||
use crate::skip_if_unsupported;
|
||||
use claudia_lib::sandbox::executor::SandboxExecutor;
|
||||
use claudia_lib::sandbox::profile::ProfileBuilder;
|
||||
use gaol::profile::{Profile, Operation, PathPattern};
|
||||
use serial_test::serial;
|
||||
use tempfile::TempDir;
|
||||
|
||||
/// Test allowed file read operations
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_allowed_file_read() {
|
||||
skip_if_unsupported!();
|
||||
|
||||
let platform = PlatformConfig::current();
|
||||
if !platform.supports_file_read {
|
||||
eprintln!("Skipping test: file read not supported on this platform");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create test file system
|
||||
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
||||
|
||||
// Create profile allowing project path access
|
||||
let operations = vec![
|
||||
Operation::FileReadAll(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 reads from allowed path
|
||||
let test_code = test_code::file_read(&test_fs.project_path.join("main.rs").to_string_lossy());
|
||||
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let binary_path = create_test_binary("test_file_read", &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");
|
||||
assert!(status.success(), "Allowed file read should succeed");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test forbidden file read operations
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_forbidden_file_read() {
|
||||
skip_if_unsupported!();
|
||||
|
||||
let platform = PlatformConfig::current();
|
||||
if !platform.supports_file_read {
|
||||
eprintln!("Skipping test: file read not supported on this platform");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create test file system
|
||||
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
||||
|
||||
// Create profile allowing only project path (not forbidden path)
|
||||
let operations = vec![
|
||||
Operation::FileReadAll(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 reads from forbidden path
|
||||
let forbidden_file = test_fs.forbidden_path.join("secret.txt");
|
||||
let test_code = test_code::file_read(&forbidden_file.to_string_lossy());
|
||||
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let binary_path = create_test_binary("test_forbidden_read", &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");
|
||||
// On some platforms (like macOS), gaol might not block all file reads
|
||||
// so we check if the operation failed OR if it's a platform limitation
|
||||
if status.success() {
|
||||
eprintln!("WARNING: File read was not blocked - this might be a platform limitation");
|
||||
// Check if we're on a platform where this is expected
|
||||
let platform_config = PlatformConfig::current();
|
||||
if !platform_config.supports_file_read {
|
||||
panic!("File read should have been blocked on this platform");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test file write operations (should always be forbidden)
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_file_write_always_forbidden() {
|
||||
skip_if_unsupported!();
|
||||
|
||||
// Create test file system
|
||||
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
||||
|
||||
// Create profile with file read permissions (write should still be blocked)
|
||||
let operations = vec![
|
||||
Operation::FileReadAll(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 write a file
|
||||
let write_path = test_fs.project_path.join("test_write.txt");
|
||||
let test_code = test_code::file_write(&write_path.to_string_lossy());
|
||||
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let binary_path = create_test_binary("test_file_write", &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");
|
||||
// File writes might not be blocked on all platforms
|
||||
if status.success() {
|
||||
eprintln!("WARNING: File write was not blocked - checking platform capabilities");
|
||||
// On macOS, file writes might not be fully blocked by gaol
|
||||
if std::env::consts::OS != "macos" {
|
||||
panic!("File write should have been blocked on this platform");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test file metadata operations
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_file_metadata_operations() {
|
||||
skip_if_unsupported!();
|
||||
|
||||
let platform = PlatformConfig::current();
|
||||
if !platform.supports_metadata_read && !platform.supports_file_read {
|
||||
eprintln!("Skipping test: metadata read not supported on this platform");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create test file system
|
||||
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
||||
|
||||
// Create profile with metadata read permission
|
||||
let operations = if platform.supports_metadata_read {
|
||||
vec![
|
||||
Operation::FileReadMetadata(PathPattern::Subpath(test_fs.project_path.clone())),
|
||||
]
|
||||
} else {
|
||||
// On Linux, metadata is allowed if file read is allowed
|
||||
vec![
|
||||
Operation::FileReadAll(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 reads file metadata
|
||||
let test_file = test_fs.project_path.join("main.rs");
|
||||
let test_code = test_code::file_metadata(&test_file.to_string_lossy());
|
||||
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let binary_path = create_test_binary("test_metadata", &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");
|
||||
if platform.supports_metadata_read || platform.supports_file_read {
|
||||
assert!(status.success(), "Metadata read should succeed when allowed");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test template variable expansion in file paths
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_template_variable_expansion() {
|
||||
skip_if_unsupported!();
|
||||
|
||||
let platform = PlatformConfig::current();
|
||||
if !platform.supports_file_read {
|
||||
eprintln!("Skipping test: file read not supported on this platform");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create test database and profile
|
||||
let test_db = TEST_DB.lock();
|
||||
test_db.reset().expect("Failed to reset database");
|
||||
|
||||
// Create a profile with template variables
|
||||
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
||||
let rules = vec![
|
||||
TestRule::file_read("{{PROJECT_PATH}}", true),
|
||||
];
|
||||
|
||||
let profile_id = test_db.create_test_profile("template_test", rules)
|
||||
.expect("Failed to create test profile");
|
||||
|
||||
// Load and build the profile
|
||||
let db_rules = claudia_lib::sandbox::profile::load_profile_rules(&test_db.conn, profile_id)
|
||||
.expect("Failed to load profile rules");
|
||||
|
||||
let builder = ProfileBuilder::new(test_fs.project_path.clone())
|
||||
.expect("Failed to create profile builder");
|
||||
|
||||
let profile = match builder.build_profile(db_rules) {
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
eprintln!("Failed to build profile with templates");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Create test binary that reads from project path
|
||||
let test_code = test_code::file_read(&test_fs.project_path.join("main.rs").to_string_lossy());
|
||||
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let binary_path = create_test_binary("test_template", &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");
|
||||
assert!(status.success(), "Template-based file access should work");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
||||
}
|
||||
}
|
||||
}
|
11
src-tauri/tests/sandbox/integration/mod.rs
Normal file
11
src-tauri/tests/sandbox/integration/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//! Integration tests for sandbox functionality
|
||||
#[cfg(test)]
|
||||
mod file_operations;
|
||||
#[cfg(test)]
|
||||
mod network_operations;
|
||||
#[cfg(test)]
|
||||
mod system_info;
|
||||
#[cfg(test)]
|
||||
mod process_isolation;
|
||||
#[cfg(test)]
|
||||
mod violations;
|
301
src-tauri/tests/sandbox/integration/network_operations.rs
Normal file
301
src-tauri/tests/sandbox/integration/network_operations.rs
Normal file
@@ -0,0 +1,301 @@
|
||||
//! 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);
|
||||
}
|
234
src-tauri/tests/sandbox/integration/process_isolation.rs
Normal file
234
src-tauri/tests/sandbox/integration/process_isolation.rs
Normal file
@@ -0,0 +1,234 @@
|
||||
//! Integration tests for process isolation in sandbox
|
||||
use crate::sandbox::common::*;
|
||||
use crate::skip_if_unsupported;
|
||||
use claudia_lib::sandbox::executor::SandboxExecutor;
|
||||
use gaol::profile::{Profile, Operation, PathPattern, AddressPattern};
|
||||
use serial_test::serial;
|
||||
use tempfile::TempDir;
|
||||
|
||||
/// Test that process spawning is always forbidden
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_process_spawn_forbidden() {
|
||||
skip_if_unsupported!();
|
||||
|
||||
// Create test project
|
||||
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
||||
|
||||
// Create profile with various permissions (process spawn should still be blocked)
|
||||
let operations = vec![
|
||||
Operation::FileReadAll(PathPattern::Subpath(test_fs.project_path.clone())),
|
||||
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 tries to spawn a process
|
||||
let test_code = test_code::spawn_process();
|
||||
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let binary_path = create_test_binary("test_spawn", 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");
|
||||
// Process spawning might not be blocked on all platforms
|
||||
if status.success() {
|
||||
eprintln!("WARNING: Process spawning was not blocked");
|
||||
// macOS sandbox might have limitations
|
||||
if std::env::consts::OS != "linux" {
|
||||
eprintln!("Process spawning might not be fully blocked on {}", std::env::consts::OS);
|
||||
} else {
|
||||
panic!("Process spawning should be blocked on Linux");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test that fork is blocked
|
||||
#[test]
|
||||
#[serial]
|
||||
#[cfg(unix)]
|
||||
fn test_fork_forbidden() {
|
||||
skip_if_unsupported!();
|
||||
|
||||
// Create test project
|
||||
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
||||
|
||||
// Create minimal profile
|
||||
let operations = vec![
|
||||
Operation::FileReadAll(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 fork
|
||||
let test_code = test_code::fork_process();
|
||||
|
||||
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let binary_path = create_test_binary_with_deps("test_fork", test_code, binary_dir.path(), &[("libc", "0.2")])
|
||||
.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");
|
||||
// Fork might not be blocked on all platforms
|
||||
if status.success() {
|
||||
eprintln!("WARNING: Fork was not blocked (platform limitation)");
|
||||
if std::env::consts::OS == "linux" {
|
||||
panic!("Fork should be blocked on Linux");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test that exec is blocked
|
||||
#[test]
|
||||
#[serial]
|
||||
#[cfg(unix)]
|
||||
fn test_exec_forbidden() {
|
||||
skip_if_unsupported!();
|
||||
|
||||
// Create test project
|
||||
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
||||
|
||||
// Create minimal profile
|
||||
let operations = vec![
|
||||
Operation::FileReadAll(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 exec
|
||||
let test_code = test_code::exec_process();
|
||||
|
||||
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let binary_path = create_test_binary_with_deps("test_exec", test_code, binary_dir.path(), &[("libc", "0.2")])
|
||||
.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");
|
||||
// Exec might not be blocked on all platforms
|
||||
if status.success() {
|
||||
eprintln!("WARNING: Exec was not blocked (platform limitation)");
|
||||
if std::env::consts::OS == "linux" {
|
||||
panic!("Exec should be blocked on Linux");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test thread creation is allowed
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_thread_creation_allowed() {
|
||||
skip_if_unsupported!();
|
||||
|
||||
// Create test project
|
||||
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
||||
|
||||
// Create minimal profile
|
||||
let operations = vec![
|
||||
Operation::FileReadAll(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 creates threads
|
||||
let test_code = r#"
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
let handle = thread::spawn(|| {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
42
|
||||
});
|
||||
|
||||
match handle.join() {
|
||||
Ok(value) => {
|
||||
println!("SUCCESS: Thread returned {}", value);
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!("FAILURE: Thread panicked");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let binary_path = create_test_binary("test_thread", 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");
|
||||
assert!(status.success(), "Thread creation should be allowed");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
||||
}
|
||||
}
|
||||
}
|
144
src-tauri/tests/sandbox/integration/system_info.rs
Normal file
144
src-tauri/tests/sandbox/integration/system_info.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
//! Integration tests for system information operations in sandbox
|
||||
use crate::sandbox::common::*;
|
||||
use crate::skip_if_unsupported;
|
||||
use claudia_lib::sandbox::executor::SandboxExecutor;
|
||||
use gaol::profile::{Profile, Operation};
|
||||
use serial_test::serial;
|
||||
use tempfile::TempDir;
|
||||
|
||||
/// Test system info read operations
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_system_info_read() {
|
||||
skip_if_unsupported!();
|
||||
|
||||
let platform = PlatformConfig::current();
|
||||
if !platform.supports_system_info {
|
||||
eprintln!("Skipping test: system info read not supported on this platform");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create test project
|
||||
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
||||
|
||||
// Create profile allowing system info read
|
||||
let operations = vec![
|
||||
Operation::SystemInfoRead,
|
||||
];
|
||||
|
||||
let profile = match Profile::new(operations) {
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
eprintln!("Failed to create profile - operation not supported");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Create test binary that reads system info
|
||||
let test_code = test_code::system_info();
|
||||
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let binary_path = create_test_binary("test_sysinfo", 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");
|
||||
assert!(status.success(), "System info read should succeed when allowed");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test forbidden system info access
|
||||
#[test]
|
||||
#[serial]
|
||||
#[cfg(target_os = "macos")]
|
||||
fn test_forbidden_system_info() {
|
||||
// Create test project
|
||||
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
||||
|
||||
// Create profile without system info permission
|
||||
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 reads system info
|
||||
let test_code = test_code::system_info();
|
||||
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let binary_path = create_test_binary("test_no_sysinfo", 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");
|
||||
// System info might not be blocked on all platforms
|
||||
if status.success() {
|
||||
eprintln!("WARNING: System info read was not blocked - checking platform");
|
||||
// On FreeBSD, system info is always allowed
|
||||
if std::env::consts::OS == "freebsd" {
|
||||
eprintln!("System info is always allowed on FreeBSD");
|
||||
} else if std::env::consts::OS == "macos" {
|
||||
// macOS might allow some system info reads
|
||||
eprintln!("System info read allowed on macOS (platform limitation)");
|
||||
} else {
|
||||
panic!("System info read should have been blocked on Linux");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test platform-specific system info behavior
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_platform_specific_system_info() {
|
||||
skip_if_unsupported!();
|
||||
|
||||
let platform = PlatformConfig::current();
|
||||
|
||||
match std::env::consts::OS {
|
||||
"linux" => {
|
||||
// On Linux, system info is never allowed
|
||||
assert!(!platform.supports_system_info,
|
||||
"Linux should not support system info read");
|
||||
}
|
||||
"macos" => {
|
||||
// On macOS, system info can be allowed
|
||||
assert!(platform.supports_system_info,
|
||||
"macOS should support system info read");
|
||||
}
|
||||
"freebsd" => {
|
||||
// On FreeBSD, system info is always allowed (can't be restricted)
|
||||
assert!(platform.supports_system_info,
|
||||
"FreeBSD always allows system info read");
|
||||
}
|
||||
_ => {
|
||||
eprintln!("Unknown platform behavior for system info");
|
||||
}
|
||||
}
|
||||
}
|
278
src-tauri/tests/sandbox/integration/violations.rs
Normal file
278
src-tauri/tests/sandbox/integration/violations.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
//! Integration tests for sandbox violation detection and logging
|
||||
use crate::sandbox::common::*;
|
||||
use crate::skip_if_unsupported;
|
||||
use claudia_lib::sandbox::executor::SandboxExecutor;
|
||||
use gaol::profile::{Profile, Operation, PathPattern};
|
||||
use serial_test::serial;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tempfile::TempDir;
|
||||
|
||||
/// Mock violation collector for testing
|
||||
#[derive(Clone)]
|
||||
struct ViolationCollector {
|
||||
violations: Arc<Mutex<Vec<ViolationEvent>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
struct ViolationEvent {
|
||||
operation_type: String,
|
||||
pattern_value: Option<String>,
|
||||
process_name: String,
|
||||
}
|
||||
|
||||
impl ViolationCollector {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
violations: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
|
||||
fn record(&self, operation_type: &str, pattern_value: Option<&str>, process_name: &str) {
|
||||
let event = ViolationEvent {
|
||||
operation_type: operation_type.to_string(),
|
||||
pattern_value: pattern_value.map(|s| s.to_string()),
|
||||
process_name: process_name.to_string(),
|
||||
};
|
||||
|
||||
if let Ok(mut violations) = self.violations.lock() {
|
||||
violations.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_violations(&self) -> Vec<ViolationEvent> {
|
||||
self.violations.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Test that violations are detected for forbidden operations
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_violation_detection() {
|
||||
skip_if_unsupported!();
|
||||
|
||||
let platform = PlatformConfig::current();
|
||||
if !platform.supports_file_read {
|
||||
eprintln!("Skipping test: file read not supported on this platform");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create test file system
|
||||
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
||||
let collector = ViolationCollector::new();
|
||||
|
||||
// Create profile allowing only project path
|
||||
let operations = vec![
|
||||
Operation::FileReadAll(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;
|
||||
}
|
||||
};
|
||||
|
||||
// Test various forbidden operations
|
||||
let test_cases = vec![
|
||||
("file_read", test_code::file_read(&test_fs.forbidden_path.join("secret.txt").to_string_lossy()), "file_read_forbidden"),
|
||||
("file_write", test_code::file_write(&test_fs.project_path.join("new.txt").to_string_lossy()), "file_write_forbidden"),
|
||||
("process_spawn", test_code::spawn_process().to_string(), "process_spawn_forbidden"),
|
||||
];
|
||||
|
||||
for (op_type, test_code, binary_name) in test_cases {
|
||||
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let binary_path = create_test_binary(binary_name, &test_code, binary_dir.path())
|
||||
.expect("Failed to create test binary");
|
||||
|
||||
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) => {
|
||||
let status = child.wait().expect("Failed to wait for child");
|
||||
if !status.success() {
|
||||
// Record violation
|
||||
collector.record(op_type, None, binary_name);
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// Sandbox setup failure, not a violation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify violations were detected
|
||||
let violations = collector.get_violations();
|
||||
// On some platforms (like macOS), sandbox might not block all operations
|
||||
if violations.is_empty() {
|
||||
eprintln!("WARNING: No violations detected - this might be a platform limitation");
|
||||
// On Linux, we expect at least some violations
|
||||
if std::env::consts::OS == "linux" {
|
||||
panic!("Should have detected some violations on Linux");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test violation patterns and details
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_violation_patterns() {
|
||||
skip_if_unsupported!();
|
||||
|
||||
let platform = PlatformConfig::current();
|
||||
if !platform.supports_file_read {
|
||||
eprintln!("Skipping test: file read not supported on this platform");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create test file system
|
||||
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
||||
|
||||
// Create profile with specific allowed paths
|
||||
let allowed_dir = test_fs.root.path().join("allowed_specific");
|
||||
std::fs::create_dir_all(&allowed_dir).expect("Failed to create allowed dir");
|
||||
|
||||
let operations = vec![
|
||||
Operation::FileReadAll(PathPattern::Subpath(test_fs.project_path.clone())),
|
||||
Operation::FileReadAll(PathPattern::Literal(allowed_dir.join("file.txt"))),
|
||||
];
|
||||
|
||||
let profile = match Profile::new(operations) {
|
||||
Ok(p) => p,
|
||||
Err(_) => {
|
||||
eprintln!("Failed to create profile - operation not supported");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Test accessing different forbidden paths
|
||||
let forbidden_db_path = test_fs.forbidden_path.join("data.db").to_string_lossy().to_string();
|
||||
let forbidden_paths = vec![
|
||||
("/etc/passwd", "system_file"),
|
||||
("/tmp/test.txt", "temp_file"),
|
||||
(forbidden_db_path.as_str(), "forbidden_db"),
|
||||
];
|
||||
|
||||
for (path, test_name) in forbidden_paths {
|
||||
let test_code = test_code::file_read(path);
|
||||
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let binary_path = create_test_binary(test_name, &test_code, binary_dir.path())
|
||||
.expect("Failed to create test binary");
|
||||
|
||||
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) => {
|
||||
let status = child.wait().expect("Failed to wait for child");
|
||||
// Some platforms might not block all file access
|
||||
if status.success() {
|
||||
eprintln!("WARNING: Access to {} was allowed (possible platform limitation)", path);
|
||||
if std::env::consts::OS == "linux" && path.starts_with("/etc") {
|
||||
panic!("Access to {} should be denied on Linux", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
// Sandbox setup failure
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test multiple violations in sequence
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_multiple_violations_sequence() {
|
||||
skip_if_unsupported!();
|
||||
|
||||
// Create test file system
|
||||
let test_fs = TestFileSystem::new().expect("Failed to create test filesystem");
|
||||
|
||||
// Create minimal profile
|
||||
let operations = vec![
|
||||
Operation::FileReadAll(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 attempts multiple forbidden operations
|
||||
let test_code = r#"
|
||||
use std::fs;
|
||||
use std::net::TcpStream;
|
||||
use std::process::Command;
|
||||
|
||||
fn main() {{
|
||||
let mut failures = 0;
|
||||
|
||||
// Try file write
|
||||
if fs::write("/tmp/test.txt", "data").is_err() {{
|
||||
eprintln!("File write failed (expected)");
|
||||
failures += 1;
|
||||
}}
|
||||
|
||||
// Try network connection
|
||||
if TcpStream::connect("google.com:80").is_err() {{
|
||||
eprintln!("Network connection failed (expected)");
|
||||
failures += 1;
|
||||
}}
|
||||
|
||||
// Try process spawn
|
||||
if Command::new("ls").output().is_err() {{
|
||||
eprintln!("Process spawn failed (expected)");
|
||||
failures += 1;
|
||||
}}
|
||||
|
||||
// Try forbidden file read
|
||||
if fs::read_to_string("/etc/passwd").is_err() {{
|
||||
eprintln!("Forbidden file read failed (expected)");
|
||||
failures += 1;
|
||||
}}
|
||||
|
||||
if failures > 0 {{
|
||||
eprintln!("FAILURE: {{failures}} operations were blocked");
|
||||
std::process::exit(1);
|
||||
}} else {{
|
||||
println!("SUCCESS: No operations were blocked (unexpected)");
|
||||
}}
|
||||
}}
|
||||
"#;
|
||||
|
||||
let binary_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let binary_path = create_test_binary("test_multi_violations", 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");
|
||||
// Multiple operations might not be blocked on all platforms
|
||||
if status.success() {
|
||||
eprintln!("WARNING: Forbidden operations were not blocked (platform limitation)");
|
||||
if std::env::consts::OS == "linux" {
|
||||
panic!("Operations should be blocked on Linux");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Sandbox execution failed: {} (may be expected in CI)", e);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user