1use crate::domain_types::{MaxExports, MaxImportFunctions};
2use nutype::nutype;
3#[allow(unused_imports)]
4use serde::{Deserialize, Serialize};
5use std::collections::HashSet;
6
7#[nutype(
8 validate(len_char_min = 1, len_char_max = 255),
9 derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Display)
10)]
11pub struct SafeFunctionName(String);
12
13impl SafeFunctionName {
14 pub fn is_messaging_function(&self) -> bool {
15 self.to_string().starts_with("agent_message_")
16 }
17
18 pub fn is_standard_function(&self) -> bool {
19 const STANDARD_FUNCTIONS: &[&str] = &[
20 "agent_get_id",
21 "agent_get_timestamp",
22 "agent_log",
23 "agent_message_send",
24 "agent_message_receive",
25 ];
26 STANDARD_FUNCTIONS.contains(&self.to_string().as_str())
27 }
28}
29
30#[nutype(
31 validate(len_char_min = 1, len_char_max = 255),
32 derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Display)
33)]
34pub struct UnsafeFunctionName(String);
35
36impl UnsafeFunctionName {
37 pub fn is_memory_function(&self) -> bool {
38 self.to_string().starts_with("memory_")
39 }
40
41 pub fn is_system_function(&self) -> bool {
42 self.to_string().starts_with("system_") || self.to_string().starts_with("process_")
43 }
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Hash)]
47pub enum FunctionName {
48 Safe(SafeFunctionName),
49 Unsafe(UnsafeFunctionName),
50}
51
52impl FunctionName {
53 pub fn categorize_function(name: &str) -> Self {
59 const UNSAFE_FUNCTIONS: &[&str] = &[
60 "memory_grow",
61 "memory_copy",
62 "table_grow",
63 "table_copy",
64 "process_exit",
65 "system_call",
66 "fd_write",
67 "fd_read",
68 "environ_get",
69 "environ_sizes_get",
70 ];
71
72 if UNSAFE_FUNCTIONS.contains(&name) {
73 FunctionName::Unsafe(UnsafeFunctionName::try_new(name.to_string()).unwrap())
74 } else {
75 SafeFunctionName::try_new(name.to_string()).map_or_else(
76 |_| FunctionName::Unsafe(UnsafeFunctionName::try_new(name.to_string()).unwrap()),
77 FunctionName::Safe,
78 )
79 }
80 }
81
82 pub fn is_safe(&self) -> bool {
83 matches!(self, FunctionName::Safe(_))
84 }
85
86 pub fn as_str(&self) -> String {
87 match self {
88 FunctionName::Safe(name) => name.to_string(),
89 FunctionName::Unsafe(name) => name.to_string(),
90 }
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct StrictSecurityPolicy {
96 pub max_import_functions: MaxImportFunctions,
97 pub max_exports: MaxExports,
98 pub allowed_functions: Vec<SafeFunctionName>,
99}
100
101impl StrictSecurityPolicy {
102 pub fn new(max_import_functions: MaxImportFunctions, max_exports: MaxExports) -> Self {
108 let allowed_functions = vec![
109 SafeFunctionName::try_new("agent_get_id".to_string()).unwrap(),
110 SafeFunctionName::try_new("agent_get_timestamp".to_string()).unwrap(),
111 SafeFunctionName::try_new("agent_log".to_string()).unwrap(),
112 ];
113
114 Self {
115 max_import_functions,
116 max_exports,
117 allowed_functions,
118 }
119 }
120
121 pub fn enable_networking(&self) -> bool {
122 false
123 }
124
125 pub fn enable_threads(&self) -> bool {
126 false
127 }
128
129 pub fn enable_fuel_metering(&self) -> bool {
130 true
131 }
132
133 pub fn is_function_allowed(&self, function: &FunctionName) -> bool {
134 match function {
135 FunctionName::Safe(name) => self.allowed_functions.contains(name),
136 FunctionName::Unsafe(_) => false,
137 }
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
142pub struct RelaxedSecurityPolicy {
143 pub max_import_functions: MaxImportFunctions,
144 pub max_exports: MaxExports,
145 pub allowed_functions: HashSet<FunctionName>,
146 pub enable_threads: bool,
147 pub enable_networking: bool,
148}
149
150impl RelaxedSecurityPolicy {
151 pub fn new(
157 max_import_functions: MaxImportFunctions,
158 max_exports: MaxExports,
159 enable_threads: bool,
160 enable_networking: bool,
161 ) -> Self {
162 let mut allowed_functions = HashSet::new();
163
164 allowed_functions.insert(FunctionName::Safe(
165 SafeFunctionName::try_new("agent_get_id".to_string()).unwrap(),
166 ));
167 allowed_functions.insert(FunctionName::Safe(
168 SafeFunctionName::try_new("agent_get_timestamp".to_string()).unwrap(),
169 ));
170 allowed_functions.insert(FunctionName::Safe(
171 SafeFunctionName::try_new("agent_log".to_string()).unwrap(),
172 ));
173 allowed_functions.insert(FunctionName::Safe(
174 SafeFunctionName::try_new("agent_message_send".to_string()).unwrap(),
175 ));
176 allowed_functions.insert(FunctionName::Safe(
177 SafeFunctionName::try_new("agent_message_receive".to_string()).unwrap(),
178 ));
179
180 if enable_networking {
181 allowed_functions.insert(FunctionName::Safe(
182 SafeFunctionName::try_new("network_connect".to_string()).unwrap(),
183 ));
184 allowed_functions.insert(FunctionName::Safe(
185 SafeFunctionName::try_new("network_send".to_string()).unwrap(),
186 ));
187 allowed_functions.insert(FunctionName::Safe(
188 SafeFunctionName::try_new("network_receive".to_string()).unwrap(),
189 ));
190 }
191
192 Self {
193 max_import_functions,
194 max_exports,
195 allowed_functions,
196 enable_threads,
197 enable_networking,
198 }
199 }
200
201 pub fn enable_fuel_metering(&self) -> bool {
202 !self.enable_threads
203 }
204
205 pub fn is_function_allowed(&self, function: &FunctionName) -> bool {
206 self.allowed_functions.contains(function)
207 }
208}
209
210#[derive(Debug, Clone, PartialEq, Eq)]
211pub enum SecurityLevel {
212 Strict(StrictSecurityPolicy),
213 Relaxed(RelaxedSecurityPolicy),
214}
215
216impl SecurityLevel {
217 pub fn validate(&self) -> bool {
218 match self {
219 SecurityLevel::Strict(policy) => {
220 policy.max_import_functions.into_inner() > 0 && policy.max_exports.into_inner() > 0
221 }
222 SecurityLevel::Relaxed(policy) => {
223 (policy.enable_fuel_metering() || !policy.enable_threads)
224 && policy.max_exports.into_inner() > 0
225 && policy.max_import_functions.into_inner() > 0
226 }
227 }
228 }
229
230 pub fn is_function_allowed(&self, function: &FunctionName) -> bool {
231 match self {
232 SecurityLevel::Strict(policy) => policy.is_function_allowed(function),
233 SecurityLevel::Relaxed(policy) => policy.is_function_allowed(function),
234 }
235 }
236}
237
238pub struct ValidatedSecurityPolicy {
239 level: SecurityLevel,
240}
241
242impl ValidatedSecurityPolicy {
243 pub fn new(level: SecurityLevel) -> Option<Self> {
244 if level.validate() {
245 Some(Self { level })
246 } else {
247 None
248 }
249 }
250
251 pub fn level(&self) -> &SecurityLevel {
252 &self.level
253 }
254}