use anyhow::{bail, Result};
use serde_json::Value;
pub fn validate_path(path: &str, allow_absolute: bool) -> Result<()> {
if path.contains('\0') {
bail!("Path contains null byte");
}
if path.contains("..") {
bail!("Path traversal detected: '..' not allowed");
}
if !allow_absolute && (path.starts_with('/') || path.starts_with('~')) {
bail!("Absolute paths not allowed");
}
Ok(())
}
pub fn validate_command_arg(arg: &str) -> Result<()> {
if arg.contains('\0') {
bail!("Argument contains null byte");
}
const SUSPICIOUS_PATTERNS: &[&str] = &[
"$(", "`", "${", "&&", "||", ";", "|", ">", "<", "\n", "\r", ];
for pattern in SUSPICIOUS_PATTERNS {
if arg.contains(pattern) {
tracing::warn!("Suspicious pattern '{}' in argument: {}", pattern, arg);
}
}
Ok(())
}
pub fn validate_typed_value(value: &Value, expected_type: &str) -> Result<()> {
match (expected_type, value) {
("string", Value::String(s)) => {
validate_command_arg(s)?;
}
("number", Value::Number(_)) => {
}
("boolean", Value::Bool(_)) => {
}
("array", Value::Array(arr)) => {
for item in arr {
if let Value::String(s) = item {
validate_command_arg(s)?;
}
}
}
_ => {
bail!("Type mismatch: expected {}, got {:?}", expected_type, value);
}
}
Ok(())
}
#[allow(dead_code)]
pub fn check_rate_limit(tool_name: &str, window_ms: u64) -> Result<()> {
tracing::debug!("Rate limit check for {} ({}ms window)", tool_name, window_ms);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path_validation() {
assert!(validate_path("file.txt", false).is_ok());
assert!(validate_path("dir/file.txt", false).is_ok());
assert!(validate_path("/etc/passwd", true).is_ok());
assert!(validate_path("../etc/passwd", false).is_err());
assert!(validate_path("/etc/passwd", false).is_err());
assert!(validate_path("~/ssh/config", false).is_err());
assert!(validate_path("file\0.txt", false).is_err());
}
#[test]
fn test_command_validation() {
assert!(validate_command_arg("hello world").is_ok());
assert!(validate_command_arg("--flag=value").is_ok());
assert!(validate_command_arg("test; ls").is_ok());
assert!(validate_command_arg("$(whoami)").is_ok());
assert!(validate_command_arg("test\0null").is_err());
}
}