zeroclaw/src/skills/symlink_tests.rs
argenis de la rosa dbf02291b4 fix: escape AppleScript target parameter in iMessage channel
- Add escape_applescript() function to prevent injection attacks
- Add is_valid_imessage_target() validation for phone/email patterns
- Update send() method to escape both message AND target parameters
- Add 40 comprehensive tests covering injection edge cases
- Addresses CWE-78 (OS Command Injection) vulnerability

Fixes #29
2026-02-14 13:38:13 -05:00

109 lines
4.2 KiB
Rust

#[cfg(test)]
mod symlink_tests {
use crate::skills::skills_dir;
use std::path::Path;
use tempfile::TempDir;
#[test]
fn test_skills_symlink_unix_edge_cases() {
let tmp = TempDir::new().unwrap();
let workspace_dir = tmp.path().join("workspace");
std::fs::create_dir_all(&workspace_dir).unwrap();
let skills_path = skills_dir(&workspace_dir);
std::fs::create_dir_all(&skills_path).unwrap();
// Test case 1: Valid symlink creation on Unix
#[cfg(unix)]
{
let source_dir = tmp.path().join("source_skill");
std::fs::create_dir_all(&source_dir).unwrap();
std::fs::write(source_dir.join("SKILL.md"), "# Test Skill\nContent").unwrap();
let dest_link = skills_path.join("linked_skill");
// Create symlink
let result = std::os::unix::fs::symlink(&source_dir, &dest_link);
assert!(result.is_ok(), "Symlink creation should succeed");
// Verify symlink works
assert!(dest_link.exists());
assert!(dest_link.is_symlink());
// Verify we can read through symlink
let content = std::fs::read_to_string(dest_link.join("SKILL.md"));
assert!(content.is_ok());
assert!(content.unwrap().contains("Test Skill"));
// Test case 2: Symlink to non-existent target should fail gracefully
let broken_link = skills_path.join("broken_skill");
let non_existent = tmp.path().join("non_existent");
let result = std::os::unix::fs::symlink(&non_existent, &broken_link);
assert!(
result.is_ok(),
"Symlink creation should succeed even if target doesn't exist"
);
// But reading through it should fail
let content = std::fs::read_to_string(broken_link.join("SKILL.md"));
assert!(content.is_err());
}
// Test case 3: Non-Unix platforms should handle symlink errors gracefully
#[cfg(not(unix))]
{
let source_dir = tmp.path().join("source_skill");
std::fs::create_dir_all(&source_dir).unwrap();
let dest_link = skills_path.join("linked_skill");
// Symlink should fail on non-Unix
let result = std::os::unix::fs::symlink(&source_dir, &dest_link);
assert!(result.is_err());
// Directory should not exist
assert!(!dest_link.exists());
}
// Test case 4: skills_dir function edge cases
let workspace_with_trailing_slash = format!("{}/", workspace_dir.display());
let path_from_str = skills_dir(Path::new(&workspace_with_trailing_slash));
assert_eq!(path_from_str, skills_path);
// Test case 5: Empty workspace directory
let empty_workspace = tmp.path().join("empty");
let empty_skills_path = skills_dir(&empty_workspace);
assert_eq!(empty_skills_path, empty_workspace.join("skills"));
assert!(!empty_skills_path.exists());
}
#[test]
fn test_skills_symlink_permissions_and_safety() {
let tmp = TempDir::new().unwrap();
let workspace_dir = tmp.path().join("workspace");
std::fs::create_dir_all(&workspace_dir).unwrap();
let skills_path = skills_dir(&workspace_dir);
std::fs::create_dir_all(&skills_path).unwrap();
#[cfg(unix)]
{
// Test case: Symlink outside workspace should be allowed (user responsibility)
let outside_dir = tmp.path().join("outside_skill");
std::fs::create_dir_all(&outside_dir).unwrap();
std::fs::write(outside_dir.join("SKILL.md"), "# Outside Skill\nContent").unwrap();
let dest_link = skills_path.join("outside_skill");
let result = std::os::unix::fs::symlink(&outside_dir, &dest_link);
assert!(
result.is_ok(),
"Should allow symlinking to directories outside workspace"
);
// Should still be readable
let content = std::fs::read_to_string(dest_link.join("SKILL.md"));
assert!(content.is_ok());
assert!(content.unwrap().contains("Outside Skill"));
}
}
}