use soft_canonicalize::{anchored_canonicalize, soft_canonicalize};
use std::path::{Path, PathBuf};
fn validate_user_path_manual(user_input: &str, jail_dir: &Path) -> Result<PathBuf, String> {
println!(" [Manual] Validating user input: {user_input:?}");
let canonical_path =
soft_canonicalize(Path::new(user_input)).map_err(|e| format!("Invalid path: {e}"))?;
println!(" [Manual] Canonicalized to: {canonical_path:?}");
if canonical_path.starts_with(jail_dir) {
println!(" [Manual] ✅ SAFE: Path is within jail boundary");
Ok(canonical_path)
} else {
println!(" [Manual] 🚫 BLOCKED: Path escapes jail boundary");
Err("Path escapes jail boundary".to_string())
}
}
fn validate_user_path_anchored(user_input: &str, jail_dir: &Path) -> Result<PathBuf, String> {
println!(" [Anchored] Validating user input: {user_input:?}");
match anchored_canonicalize(jail_dir, user_input) {
Ok(safe_path) => {
println!(" [Anchored] Resolved to: {safe_path:?}");
println!(" [Anchored] ✅ SAFE: Path is automatically constrained to anchor");
Ok(safe_path)
}
Err(e) => {
println!(" [Anchored] 🚫 BLOCKED: {e}");
Err(format!("Path validation failed: {e}"))
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Soft Canonicalize - Security Demo ===\n");
let jail = std::env::temp_dir().join("user_files");
let canonical_jail = soft_canonicalize(&jail)?;
println!("Jail directory (canonical for manual demo): {canonical_jail:?}\n");
println!("--- SAFE PATHS ---");
let safe_paths = [
"documents/file.txt",
"photos/vacation/beach.jpg",
"projects/website/index.html",
"./config/settings.json",
"uploads/user123/document.pdf",
];
for path in &safe_paths {
println!("Testing path: {path:?}");
match validate_user_path_manual(path, &canonical_jail) {
Ok(_) => println!(),
Err(e) => println!(" Error: {e}\n"),
}
match validate_user_path_anchored(path, &jail) {
Ok(_) => println!(),
Err(e) => println!(" Error: {e}\n"),
}
}
println!("--- MALICIOUS PATHS (Directory Traversal) ---");
let malicious_paths = [
"../../../etc/passwd",
"documents/../../../sensitive.txt",
"uploads/../../../../../../root/.ssh/id_rsa",
"../outside_jail/malware.exe",
"safe/path/../../../../../../etc/shadow",
"normal/../../../config.ini",
"files/../../../../windows/system32/config/sam",
];
for path in &malicious_paths {
println!("Testing malicious path: {path:?}");
match validate_user_path_manual(path, &canonical_jail) {
Ok(_) => println!(),
Err(e) => println!(" [Manual] Expected: {e}\n"),
}
match validate_user_path_anchored(path, &jail) {
Ok(_) => println!(),
Err(e) => println!(" [Anchored] Expected: {e}\n"),
}
}
println!("--- ABSOLUTE PATHS OUTSIDE JAIL ---");
#[cfg(windows)]
let absolute_attacks = [
"C:\\Windows\\System32\\config\\SAM",
"D:\\sensitive\\data.txt",
"\\\\server\\share\\secrets.txt",
];
#[cfg(not(windows))]
let absolute_attacks = [
"/etc/passwd",
"/root/.ssh/authorized_keys",
"/usr/bin/malware",
"/home/other_user/secrets.txt",
];
for path in &absolute_attacks {
println!("Testing absolute attack: {path:?}");
match validate_user_path_manual(path, &canonical_jail) {
Ok(_) => println!(),
Err(e) => println!(" [Manual] Expected: {e}\n"),
}
match validate_user_path_anchored(path, &jail) {
Ok(_) => println!(),
Err(e) => println!(" [Anchored] Expected: {e}\n"),
}
}
println!("--- EDGE CASES ---");
let edge_cases = [
"", ".", "..", "file", "very/deep/nested/structure/with/many/levels/file.txt", ];
for path in &edge_cases {
println!("Testing edge case: {path:?}");
match validate_user_path_manual(path, &canonical_jail) {
Ok(_) => println!(),
Err(e) => println!(" [Manual] Result: {e}\n"),
}
match validate_user_path_anchored(path, &jail) {
Ok(_) => println!(),
Err(e) => println!(" [Anchored] Result: {e}\n"),
}
}
println!("=== Security Demo Complete ===");
println!("Both manual validation and anchored_canonicalize successfully blocked all directory traversal attempts!");
println!("anchored_canonicalize provides the same security with simpler, more reliable code.");
Ok(())
}