evalbox_sandbox/
validate.rs1use std::path::Path;
26
27use thiserror::Error;
28
29#[derive(Debug, Clone, PartialEq, Eq, Error)]
31pub enum ValidationError {
32 #[error("command cannot be empty")]
33 EmptyCommand,
34
35 #[error("argument {0} is empty")]
36 EmptyArgument(usize),
37
38 #[error("null byte in input")]
39 NullByte,
40
41 #[error("path traversal not allowed")]
42 PathTraversal,
43
44 #[error("absolute path not allowed")]
45 AbsolutePath,
46
47 #[error("path cannot be empty")]
48 EmptyPath,
49}
50
51pub fn validate_cmd(cmd: &[&str]) -> Result<(), ValidationError> {
53 if cmd.is_empty() {
54 return Err(ValidationError::EmptyCommand);
55 }
56 for (i, arg) in cmd.iter().enumerate() {
57 if arg.is_empty() {
58 return Err(ValidationError::EmptyArgument(i));
59 }
60 if arg.contains('\0') {
61 return Err(ValidationError::NullByte);
62 }
63 }
64 Ok(())
65}
66
67pub fn validate_path(path: &str) -> Result<(), ValidationError> {
69 if path.is_empty() {
70 return Err(ValidationError::EmptyPath);
71 }
72 if path.contains('\0') {
73 return Err(ValidationError::NullByte);
74 }
75 if path.starts_with('/') {
76 return Err(ValidationError::AbsolutePath);
77 }
78 if has_traversal(path) {
79 return Err(ValidationError::PathTraversal);
80 }
81 Ok(())
82}
83
84fn has_traversal(path: &str) -> bool {
85 Path::new(path)
86 .components()
87 .any(|c| matches!(c, std::path::Component::ParentDir))
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn cmd_valid() {
96 assert!(validate_cmd(&["echo", "hello"]).is_ok());
97 }
98
99 #[test]
100 fn cmd_empty() {
101 assert_eq!(validate_cmd(&[]), Err(ValidationError::EmptyCommand));
102 }
103
104 #[test]
105 fn path_traversal() {
106 assert_eq!(
107 validate_path("../etc/passwd"),
108 Err(ValidationError::PathTraversal)
109 );
110 }
111
112 #[test]
113 fn path_absolute() {
114 assert_eq!(
115 validate_path("/etc/passwd"),
116 Err(ValidationError::AbsolutePath)
117 );
118 }
119}