Skip to main content

skilllite_core/
path_validation.rs

1//! Path validation utilities.
2//!
3//! Ensures paths stay within allowed root to prevent path traversal attacks.
4
5use crate::error::PathValidationError;
6use std::path::{Path, PathBuf};
7
8/// Get the allowed root directory for path validation.
9pub fn get_allowed_root() -> Result<PathBuf, PathValidationError> {
10    let allowed_root = crate::config::PathsConfig::from_env()
11        .skills_root
12        .map(PathBuf::from)
13        .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
14    allowed_root
15        .canonicalize()
16        .map_err(PathValidationError::InvalidRoot)
17}
18
19/// Validate path is within allowed root. Prevents path traversal.
20pub fn validate_path_under_root(
21    path: &str,
22    path_type: &str,
23) -> Result<PathBuf, PathValidationError> {
24    let allowed_root = get_allowed_root()?;
25    let input = Path::new(path);
26    let full = if input.is_absolute() {
27        input.to_path_buf()
28    } else {
29        allowed_root.join(input)
30    };
31    let canonical = full
32        .canonicalize()
33        .map_err(|_| PathValidationError::NotFound {
34            path_type: path_type.to_string(),
35            path: path.to_string(),
36        })?;
37    if !canonical.starts_with(&allowed_root) {
38        return Err(PathValidationError::PathEscape {
39            path_type: path_type.to_string(),
40            path: path.to_string(),
41        });
42    }
43    Ok(canonical)
44}
45
46/// Validate skill_dir is within allowed root. Prevents path traversal.
47pub fn validate_skill_path(skill_dir: &str) -> Result<PathBuf, PathValidationError> {
48    validate_path_under_root(skill_dir, "Skill path")
49}