1use std::collections::HashSet;
8
9#[derive(Debug, Clone)]
11pub struct SecurityPolicy {
12 pub allowed_connectors: HashSet<String>,
13 pub denied_paths: Vec<String>,
14 pub allow_network: bool,
15 pub allow_file_read: bool,
16 pub allow_file_write: bool,
17 pub sandbox_mode: bool,
18 pub allow_subprocess: bool,
19 pub allowed_commands: Vec<String>,
20}
21
22impl SecurityPolicy {
23 pub fn permissive() -> Self {
24 SecurityPolicy {
25 allowed_connectors: HashSet::new(),
26 denied_paths: Vec::new(),
27 allow_network: true,
28 allow_file_read: true,
29 allow_file_write: true,
30 sandbox_mode: false,
31 allow_subprocess: true,
32 allowed_commands: vec![],
33 }
34 }
35
36 pub fn sandbox() -> Self {
37 SecurityPolicy {
38 allowed_connectors: HashSet::new(),
39 denied_paths: Vec::new(),
40 allow_network: false,
41 allow_file_read: true,
42 allow_file_write: false,
43 sandbox_mode: true,
44 allow_subprocess: false,
45 allowed_commands: vec![],
46 }
47 }
48
49 pub fn check(&self, permission: &str) -> bool {
51 if !self.sandbox_mode {
52 return true;
53 }
54 match permission {
55 "network" => self.allow_network,
56 "file_read" => self.allow_file_read,
57 "file_write" => self.allow_file_write,
58 "python" => false,
59 "env_write" => false,
60 "subprocess" => self.allow_subprocess,
61 p if p.starts_with("command:") => {
62 let cmd = &p["command:".len()..];
63 self.check_command(cmd)
64 }
65 p if p.starts_with("connector:") => {
66 let conn_type = &p["connector:".len()..];
67 self.allowed_connectors.is_empty() || self.allowed_connectors.contains(conn_type)
68 }
69 _ => true,
70 }
71 }
72
73 pub fn check_command(&self, command: &str) -> bool {
75 if !self.allow_subprocess {
76 return false;
77 }
78 if self.allowed_commands.is_empty() {
79 return true;
80 }
81 self.allowed_commands.iter().any(|c| c == command)
82 }
83
84 pub fn check_path(&self, path: &str) -> bool {
86 if !self.sandbox_mode {
87 return true;
88 }
89 !self
90 .denied_paths
91 .iter()
92 .any(|denied| path.starts_with(denied))
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn test_permissive_allows_all() {
102 let policy = SecurityPolicy::permissive();
103 assert!(policy.check("network"));
104 assert!(policy.check("file_read"));
105 assert!(policy.check("file_write"));
106 assert!(policy.check("connector:postgres"));
107 }
108
109 #[test]
110 fn test_sandbox_restricts() {
111 let policy = SecurityPolicy::sandbox();
112 assert!(!policy.check("network"));
113 assert!(policy.check("file_read"));
114 assert!(!policy.check("file_write"));
115 }
116
117 #[test]
118 fn test_connector_whitelist() {
119 let mut policy = SecurityPolicy::sandbox();
120 policy.allowed_connectors.insert("postgres".to_string());
121 assert!(policy.check("connector:postgres"));
122 assert!(!policy.check("connector:mysql"));
123 }
124
125 #[test]
126 fn test_denied_paths() {
127 let mut policy = SecurityPolicy::sandbox();
128 policy.denied_paths.push("/etc/".to_string());
129 assert!(!policy.check_path("/etc/passwd"));
130 assert!(policy.check_path("/home/user/file.txt"));
131 }
132
133 #[test]
134 fn test_sandbox_denies_subprocess() {
135 let policy = SecurityPolicy::sandbox();
136 assert!(!policy.check("subprocess"));
137 assert!(!policy.check_command("npx"));
138 assert!(!policy.check("command:npx"));
139 }
140
141 #[test]
142 fn test_permissive_allows_subprocess() {
143 let policy = SecurityPolicy::permissive();
144 assert!(policy.check("subprocess"));
145 assert!(policy.check_command("npx"));
146 assert!(policy.check("command:npx"));
147 }
148
149 #[test]
150 fn test_command_whitelist() {
151 let mut policy = SecurityPolicy::sandbox();
152 policy.allow_subprocess = true;
153 policy.allowed_commands = vec!["npx".to_string(), "node".to_string()];
154 assert!(policy.check_command("npx"));
155 assert!(policy.check_command("node"));
156 assert!(!policy.check_command("bash"));
157 assert!(policy.check("command:npx"));
158 assert!(!policy.check("command:bash"));
159 }
160
161 #[test]
162 fn test_empty_whitelist_allows_all() {
163 let mut policy = SecurityPolicy::sandbox();
164 policy.allow_subprocess = true;
165 assert!(policy.check_command("npx"));
167 assert!(policy.check_command("anything"));
168 assert!(policy.check("command:whatever"));
169 }
170}