init: push source

This commit is contained in:
Mufeed VH
2025-06-19 19:24:01 +05:30
commit 8e76d016d4
136 changed files with 38177 additions and 0 deletions

View 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);
}
}
}

View 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;

View 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);
}

View 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);
}
}
}

View 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");
}
}
}

View 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);
}
}
}