gamecode_mcp2/
validation.rs1use anyhow::{bail, Result};
5use serde_json::Value;
6
7pub fn validate_path(path: &str, allow_absolute: bool) -> Result<()> {
9 if path.contains('\0') {
11 bail!("Path contains null byte");
12 }
13
14 if path.contains("..") {
16 bail!("Path traversal detected: '..' not allowed");
17 }
18
19 if !allow_absolute && (path.starts_with('/') || path.starts_with('~')) {
21 bail!("Absolute paths not allowed");
22 }
23
24 Ok(())
25}
26
27pub fn validate_command_arg(arg: &str) -> Result<()> {
29 if arg.contains('\0') {
31 bail!("Argument contains null byte");
32 }
33
34 const SUSPICIOUS_PATTERNS: &[&str] = &[
37 "$(", "`", "${", "&&", "||", ";", "|", ">", "<", "\n", "\r", ];
49
50 for pattern in SUSPICIOUS_PATTERNS {
51 if arg.contains(pattern) {
52 tracing::warn!("Suspicious pattern '{}' in argument: {}", pattern, arg);
54 }
55 }
56
57 Ok(())
58}
59
60pub fn validate_typed_value(value: &Value, expected_type: &str) -> Result<()> {
62 match (expected_type, value) {
63 ("string", Value::String(s)) => {
64 validate_command_arg(s)?;
65 }
66 ("number", Value::Number(_)) => {
67 }
69 ("boolean", Value::Bool(_)) => {
70 }
72 ("array", Value::Array(arr)) => {
73 for item in arr {
75 if let Value::String(s) = item {
76 validate_command_arg(s)?;
77 }
78 }
79 }
80 _ => {
81 bail!("Type mismatch: expected {}, got {:?}", expected_type, value);
82 }
83 }
84 Ok(())
85}
86
87#[allow(dead_code)]
89pub fn check_rate_limit(tool_name: &str, window_ms: u64) -> Result<()> {
90 tracing::debug!("Rate limit check for {} ({}ms window)", tool_name, window_ms);
93 Ok(())
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn test_path_validation() {
102 assert!(validate_path("file.txt", false).is_ok());
104 assert!(validate_path("dir/file.txt", false).is_ok());
105 assert!(validate_path("/etc/passwd", true).is_ok());
106
107 assert!(validate_path("../etc/passwd", false).is_err());
109 assert!(validate_path("/etc/passwd", false).is_err());
110 assert!(validate_path("~/ssh/config", false).is_err());
111 assert!(validate_path("file\0.txt", false).is_err());
112 }
113
114 #[test]
115 fn test_command_validation() {
116 assert!(validate_command_arg("hello world").is_ok());
118 assert!(validate_command_arg("--flag=value").is_ok());
119
120 assert!(validate_command_arg("test; ls").is_ok());
122 assert!(validate_command_arg("$(whoami)").is_ok());
123
124 assert!(validate_command_arg("test\0null").is_err());
126 }
127}