use anyhow::{Context, Result};
use std::path::PathBuf;
use super::checks;
use crate::ssh::ssh_config::path::expand_path_internal;
pub fn secure_validate_path(path: &str, path_type: &str, line_number: usize) -> Result<PathBuf> {
let expanded_path = expand_path_internal(path)
.with_context(|| format!("Failed to expand path '{path}' at line {line_number}"))?;
let path_str = expanded_path.to_string_lossy();
if path_str.contains("../") || path_str.contains("..\\") {
anyhow::bail!(
"Security violation: {path_type} path contains directory traversal sequence '..' at line {line_number}. \
Path traversal attacks are not allowed."
);
}
if path_str.contains('\0') {
anyhow::bail!(
"Security violation: {path_type} path contains null byte at line {line_number}. \
This could be used for path truncation attacks."
);
}
let canonical_path = if expanded_path.exists() {
match expanded_path.canonicalize() {
Ok(canonical) => canonical,
Err(e) => {
tracing::debug!(
"Could not canonicalize {} path '{}' at line {}: {}. Using expanded path as-is.",
path_type, path_str, line_number, e
);
expanded_path.clone()
}
}
} else {
expanded_path.clone()
};
let canonical_str = canonical_path.to_string_lossy();
if canonical_str.contains("..") {
if canonical_str.split('/').any(|component| component == "..")
|| canonical_str.split('\\').any(|component| component == "..")
{
anyhow::bail!(
"Security violation: Canonicalized {path_type} path '{canonical_str}' contains parent directory references at line {line_number}. \
This could indicate a path traversal attempt."
);
}
}
match path_type {
"identity" => {
checks::validate_identity_file_security(&canonical_path, line_number)?;
}
"known_hosts" => {
checks::validate_known_hosts_file_security(&canonical_path, line_number)?;
}
"certificate" => {
checks::validate_certificate_file_security(&canonical_path, line_number)?;
}
_ => {
checks::validate_general_file_security(&canonical_path, line_number)?;
}
}
Ok(canonical_path)
}