191 lines
6.4 KiB
Rust
191 lines
6.4 KiB
Rust
//! Integration tests for path normalization and containment.
|
|
//!
|
|
//! These tests use temporary directories to test real filesystem operations.
|
|
|
|
use dirigent_tools::config::SandboxConfig;
|
|
use dirigent_tools::path::{canonicalize_path, check_containment, validate_path, SymlinkPolicy};
|
|
use dirigent_tools::ToolError;
|
|
use std::fs;
|
|
use std::path::PathBuf;
|
|
use tempfile::TempDir;
|
|
|
|
#[test]
|
|
fn test_validate_path_with_real_filesystem() {
|
|
// Create a temp directory structure
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let project_dir = temp_dir.path().join("project");
|
|
let src_dir = project_dir.join("src");
|
|
fs::create_dir_all(&src_dir).unwrap();
|
|
|
|
// Create a test file
|
|
let test_file = src_dir.join("main.rs");
|
|
fs::write(&test_file, "fn main() {}").unwrap();
|
|
|
|
// Create config
|
|
let mut config = SandboxConfig::default();
|
|
config.allowed_roots = vec![project_dir.clone()];
|
|
config.blocked_paths = vec!["**/.env".to_string()];
|
|
|
|
// Test: Valid path within allowed root
|
|
let result = validate_path(test_file.to_str().unwrap(), &config);
|
|
assert!(result.is_ok());
|
|
|
|
// Test: Path at the root level (should be allowed)
|
|
let root_file = project_dir.join("README.md");
|
|
fs::write(&root_file, "# Project").unwrap();
|
|
let result = validate_path(root_file.to_str().unwrap(), &config);
|
|
assert!(result.is_ok());
|
|
|
|
// Test: Path outside allowed roots
|
|
let outside_path = temp_dir.path().join("outside.txt");
|
|
fs::write(&outside_path, "outside").unwrap();
|
|
let result = validate_path(outside_path.to_str().unwrap(), &config);
|
|
assert!(result.is_err());
|
|
assert!(matches!(result, Err(ToolError::SandboxViolation { .. })));
|
|
}
|
|
|
|
#[test]
|
|
fn test_validate_path_blocked() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let project_dir = temp_dir.path().join("project");
|
|
fs::create_dir_all(&project_dir).unwrap();
|
|
|
|
// Create a .env file
|
|
let env_file = project_dir.join(".env");
|
|
fs::write(&env_file, "SECRET=value").unwrap();
|
|
|
|
// Create config with blocklist
|
|
let mut config = SandboxConfig::default();
|
|
config.allowed_roots = vec![project_dir.clone()];
|
|
config.blocked_paths = vec!["**/.env".to_string()];
|
|
|
|
// Test: Blocked path
|
|
let result = validate_path(env_file.to_str().unwrap(), &config);
|
|
assert!(result.is_err());
|
|
assert!(matches!(result, Err(ToolError::BlockedPath { .. })));
|
|
}
|
|
|
|
#[test]
|
|
fn test_validate_path_non_existent_for_write() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let project_dir = temp_dir.path().join("project");
|
|
fs::create_dir_all(&project_dir).unwrap();
|
|
|
|
// Non-existent file (for write operations)
|
|
let new_file = project_dir.join("new_file.txt");
|
|
|
|
let mut config = SandboxConfig::default();
|
|
config.allowed_roots = vec![project_dir.clone()];
|
|
|
|
// Test: Non-existent file within allowed root
|
|
let result = validate_path(new_file.to_str().unwrap(), &config);
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn test_canonicalize_path_with_real_filesystem() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let project_dir = temp_dir.path().join("project");
|
|
fs::create_dir_all(&project_dir).unwrap();
|
|
|
|
let test_file = project_dir.join("test.txt");
|
|
fs::write(&test_file, "content").unwrap();
|
|
|
|
let policy = SymlinkPolicy::default();
|
|
|
|
// Test: Canonicalize existing file
|
|
let canonical = canonicalize_path(&test_file, &policy).unwrap();
|
|
assert!(canonical.is_absolute());
|
|
assert!(canonical.exists());
|
|
|
|
// Verify no ".." components
|
|
assert!(!canonical.to_string_lossy().contains(".."));
|
|
}
|
|
|
|
#[test]
|
|
fn test_containment_with_real_filesystem() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let root_dir = temp_dir.path().join("root");
|
|
let subdir = root_dir.join("subdir");
|
|
fs::create_dir_all(&subdir).unwrap();
|
|
|
|
let file = subdir.join("file.txt");
|
|
fs::write(&file, "content").unwrap();
|
|
|
|
// Canonicalize paths
|
|
let canonical_root = dunce::canonicalize(&root_dir).unwrap();
|
|
let canonical_file = dunce::canonicalize(&file).unwrap();
|
|
|
|
// Test: File is contained in root
|
|
let result = check_containment(&canonical_file, &[canonical_root.clone()]);
|
|
assert!(result.is_ok());
|
|
|
|
// Test: Root is not strictly contained in itself
|
|
let result = check_containment(&canonical_root, &[canonical_root.clone()]);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
fn test_symlink_handling_unix() {
|
|
use std::os::unix::fs::symlink;
|
|
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let allowed_dir = temp_dir.path().join("allowed");
|
|
let outside_dir = temp_dir.path().join("outside");
|
|
fs::create_dir_all(&allowed_dir).unwrap();
|
|
fs::create_dir_all(&outside_dir).unwrap();
|
|
|
|
// Create a file outside the allowed root
|
|
let outside_file = outside_dir.join("secret.txt");
|
|
fs::write(&outside_file, "secret").unwrap();
|
|
|
|
// Create a symlink inside allowed root pointing outside
|
|
let symlink_path = allowed_dir.join("link_to_secret");
|
|
symlink(&outside_file, &symlink_path).unwrap();
|
|
|
|
let mut config = SandboxConfig::default();
|
|
config.allowed_roots = vec![dunce::canonicalize(&allowed_dir).unwrap()];
|
|
config.allow_symlink_escape = false; // Don't allow escapes
|
|
|
|
// Test: Symlink escape should be blocked
|
|
let result = validate_path(symlink_path.to_str().unwrap(), &config);
|
|
assert!(result.is_err());
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[test]
|
|
fn test_windows_reserved_device_names() {
|
|
let policy = SymlinkPolicy::default();
|
|
|
|
// Reserved device names should be rejected
|
|
let reserved_names = vec!["CON", "PRN", "AUX", "NUL", "COM1", "COM2", "LPT1", "LPT2"];
|
|
|
|
for name in reserved_names {
|
|
let path = PathBuf::from(format!("C:\\{}", name));
|
|
let result = canonicalize_path(&path, &policy);
|
|
assert!(result.is_err(), "Should reject reserved name: {}", name);
|
|
}
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
#[test]
|
|
fn test_windows_path_forms() {
|
|
// Test various Windows path forms with temp directory
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let test_file = temp_dir.path().join("test.txt");
|
|
fs::write(&test_file, "content").unwrap();
|
|
|
|
let policy = SymlinkPolicy::default();
|
|
|
|
// Test: Standard absolute path
|
|
let canonical = canonicalize_path(&test_file, &policy).unwrap();
|
|
assert!(canonical.is_absolute());
|
|
|
|
// Test: Drive letter normalization (uppercase)
|
|
let path_str = canonical.to_string_lossy();
|
|
if path_str.len() >= 2 && path_str.chars().nth(1) == Some(':') {
|
|
assert!(path_str.chars().nth(0).unwrap().is_ascii_uppercase());
|
|
}
|
|
}
|