1use serde::Serialize;
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
28#[non_exhaustive]
29pub enum Category {
30 Fs,
32 Net,
34 Env,
36 Process,
38 Ffi,
40}
41
42impl Category {
43 pub fn label(&self) -> &'static str {
45 match self {
46 Self::Fs => "FS",
47 Self::Net => "NET",
48 Self::Env => "ENV",
49 Self::Process => "PROC",
50 Self::Ffi => "FFI",
51 }
52 }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
69#[non_exhaustive]
70pub enum Risk {
71 Low,
73 Medium,
75 High,
77 Critical,
79}
80
81impl Risk {
82 pub fn label(&self) -> &'static str {
84 match self {
85 Self::Low => "low",
86 Self::Medium => "medium",
87 Self::High => "high",
88 Self::Critical => "critical",
89 }
90 }
91
92 pub fn parse(s: &str) -> Self {
96 match s {
97 "low" => Self::Low,
98 "medium" => Self::Medium,
99 "high" => Self::High,
100 "critical" => Self::Critical,
101 _ => Self::Low,
102 }
103 }
104}
105
106#[derive(Debug, Clone)]
112pub struct Authority {
113 pub pattern: AuthorityPattern,
115 pub category: Category,
117 pub subcategory: &'static str,
119 pub risk: Risk,
121 pub description: &'static str,
123}
124
125#[derive(Debug, Clone)]
134pub enum AuthorityPattern {
135 Path(&'static [&'static str]),
141
142 MethodWithContext {
149 method: &'static str,
151 requires_path: &'static [&'static str],
153 },
154}
155
156#[derive(Debug, Clone)]
172pub struct CustomAuthority {
173 pub path: Vec<String>,
175 pub category: Category,
177 pub risk: Risk,
179 pub description: String,
181}
182
183pub fn build_registry() -> Vec<Authority> {
192 vec![
193 Authority {
195 pattern: AuthorityPattern::Path(&["std", "fs", "read"]),
196 category: Category::Fs,
197 subcategory: "read",
198 risk: Risk::Medium,
199 description: "Read arbitrary file contents",
200 },
201 Authority {
202 pattern: AuthorityPattern::Path(&["std", "fs", "read_to_string"]),
203 category: Category::Fs,
204 subcategory: "read",
205 risk: Risk::Medium,
206 description: "Read arbitrary file contents as string",
207 },
208 Authority {
209 pattern: AuthorityPattern::Path(&["std", "fs", "read_dir"]),
210 category: Category::Fs,
211 subcategory: "read",
212 risk: Risk::Low,
213 description: "List directory contents",
214 },
215 Authority {
216 pattern: AuthorityPattern::Path(&["std", "fs", "metadata"]),
217 category: Category::Fs,
218 subcategory: "read",
219 risk: Risk::Low,
220 description: "Read file metadata",
221 },
222 Authority {
223 pattern: AuthorityPattern::Path(&["std", "fs", "write"]),
224 category: Category::Fs,
225 subcategory: "write",
226 risk: Risk::High,
227 description: "Write arbitrary file contents",
228 },
229 Authority {
230 pattern: AuthorityPattern::Path(&["std", "fs", "create_dir_all"]),
231 category: Category::Fs,
232 subcategory: "write",
233 risk: Risk::Medium,
234 description: "Create directory tree",
235 },
236 Authority {
237 pattern: AuthorityPattern::Path(&["std", "fs", "remove_file"]),
238 category: Category::Fs,
239 subcategory: "write",
240 risk: Risk::High,
241 description: "Delete a file",
242 },
243 Authority {
244 pattern: AuthorityPattern::Path(&["std", "fs", "remove_dir_all"]),
245 category: Category::Fs,
246 subcategory: "write",
247 risk: Risk::Critical,
248 description: "Recursively delete a directory tree",
249 },
250 Authority {
251 pattern: AuthorityPattern::Path(&["std", "fs", "rename"]),
252 category: Category::Fs,
253 subcategory: "write",
254 risk: Risk::Medium,
255 description: "Rename/move a file",
256 },
257 Authority {
258 pattern: AuthorityPattern::Path(&["std", "fs", "copy"]),
259 category: Category::Fs,
260 subcategory: "read+write",
261 risk: Risk::Medium,
262 description: "Copy a file",
263 },
264 Authority {
266 pattern: AuthorityPattern::Path(&["File", "open"]),
267 category: Category::Fs,
268 subcategory: "read",
269 risk: Risk::Medium,
270 description: "Open a file for reading",
271 },
272 Authority {
273 pattern: AuthorityPattern::Path(&["File", "create"]),
274 category: Category::Fs,
275 subcategory: "write",
276 risk: Risk::High,
277 description: "Create/truncate a file for writing",
278 },
279 Authority {
280 pattern: AuthorityPattern::Path(&["OpenOptions", "open"]),
281 category: Category::Fs,
282 subcategory: "read+write",
283 risk: Risk::Medium,
284 description: "Open file with custom options",
285 },
286 Authority {
288 pattern: AuthorityPattern::Path(&["tokio", "fs", "read"]),
289 category: Category::Fs,
290 subcategory: "read",
291 risk: Risk::Medium,
292 description: "Async read file contents",
293 },
294 Authority {
295 pattern: AuthorityPattern::Path(&["tokio", "fs", "read_to_string"]),
296 category: Category::Fs,
297 subcategory: "read",
298 risk: Risk::Medium,
299 description: "Async read file as string",
300 },
301 Authority {
302 pattern: AuthorityPattern::Path(&["tokio", "fs", "write"]),
303 category: Category::Fs,
304 subcategory: "write",
305 risk: Risk::High,
306 description: "Async write file contents",
307 },
308 Authority {
309 pattern: AuthorityPattern::Path(&["tokio", "fs", "remove_file"]),
310 category: Category::Fs,
311 subcategory: "write",
312 risk: Risk::High,
313 description: "Async delete a file",
314 },
315 Authority {
317 pattern: AuthorityPattern::Path(&["TcpStream", "connect"]),
318 category: Category::Net,
319 subcategory: "connect",
320 risk: Risk::High,
321 description: "Open outbound TCP connection",
322 },
323 Authority {
324 pattern: AuthorityPattern::Path(&["TcpListener", "bind"]),
325 category: Category::Net,
326 subcategory: "bind",
327 risk: Risk::High,
328 description: "Bind a TCP listener to a port",
329 },
330 Authority {
331 pattern: AuthorityPattern::Path(&["UdpSocket", "bind"]),
332 category: Category::Net,
333 subcategory: "bind",
334 risk: Risk::High,
335 description: "Bind a UDP socket",
336 },
337 Authority {
339 pattern: AuthorityPattern::MethodWithContext {
340 method: "send_to",
341 requires_path: &["UdpSocket", "bind"],
342 },
343 category: Category::Net,
344 subcategory: "connect",
345 risk: Risk::High,
346 description: "Send UDP datagram to address",
347 },
348 Authority {
350 pattern: AuthorityPattern::Path(&["tokio", "net", "TcpStream", "connect"]),
351 category: Category::Net,
352 subcategory: "connect",
353 risk: Risk::High,
354 description: "Async outbound TCP connection",
355 },
356 Authority {
357 pattern: AuthorityPattern::Path(&["tokio", "net", "TcpListener", "bind"]),
358 category: Category::Net,
359 subcategory: "bind",
360 risk: Risk::High,
361 description: "Async bind TCP listener",
362 },
363 Authority {
365 pattern: AuthorityPattern::Path(&["reqwest", "get"]),
366 category: Category::Net,
367 subcategory: "connect",
368 risk: Risk::High,
369 description: "HTTP GET request",
370 },
371 Authority {
372 pattern: AuthorityPattern::Path(&["reqwest", "Client", "new"]),
373 category: Category::Net,
374 subcategory: "connect",
375 risk: Risk::Medium,
376 description: "Create HTTP client",
377 },
378 Authority {
379 pattern: AuthorityPattern::Path(&["reqwest", "Client", "get"]),
380 category: Category::Net,
381 subcategory: "connect",
382 risk: Risk::High,
383 description: "HTTP GET via client",
384 },
385 Authority {
386 pattern: AuthorityPattern::Path(&["reqwest", "Client", "post"]),
387 category: Category::Net,
388 subcategory: "connect",
389 risk: Risk::High,
390 description: "HTTP POST via client",
391 },
392 Authority {
394 pattern: AuthorityPattern::Path(&["hyper", "Client", "request"]),
395 category: Category::Net,
396 subcategory: "connect",
397 risk: Risk::High,
398 description: "Hyper HTTP request",
399 },
400 Authority {
401 pattern: AuthorityPattern::Path(&["hyper", "Server", "bind"]),
402 category: Category::Net,
403 subcategory: "bind",
404 risk: Risk::High,
405 description: "Bind Hyper HTTP server",
406 },
407 Authority {
410 pattern: AuthorityPattern::Path(&["std", "env", "var"]),
411 category: Category::Env,
412 subcategory: "read",
413 risk: Risk::Medium,
414 description: "Read environment variable",
415 },
416 Authority {
417 pattern: AuthorityPattern::Path(&["std", "env", "vars"]),
418 category: Category::Env,
419 subcategory: "read",
420 risk: Risk::Medium,
421 description: "Read all environment variables",
422 },
423 Authority {
424 pattern: AuthorityPattern::Path(&["std", "env", "set_var"]),
425 category: Category::Env,
426 subcategory: "write",
427 risk: Risk::High,
428 description: "Modify environment variable",
429 },
430 Authority {
431 pattern: AuthorityPattern::Path(&["std", "env", "remove_var"]),
432 category: Category::Env,
433 subcategory: "write",
434 risk: Risk::Medium,
435 description: "Remove environment variable",
436 },
437 Authority {
438 pattern: AuthorityPattern::Path(&["std", "env", "current_dir"]),
439 category: Category::Env,
440 subcategory: "read",
441 risk: Risk::Low,
442 description: "Read current working directory",
443 },
444 Authority {
445 pattern: AuthorityPattern::Path(&["std", "env", "set_current_dir"]),
446 category: Category::Env,
447 subcategory: "write",
448 risk: Risk::High,
449 description: "Change working directory",
450 },
451 Authority {
453 pattern: AuthorityPattern::Path(&["Command", "new"]),
454 category: Category::Process,
455 subcategory: "spawn",
456 risk: Risk::Critical,
457 description: "Create command for subprocess execution",
458 },
459 Authority {
461 pattern: AuthorityPattern::MethodWithContext {
462 method: "output",
463 requires_path: &["Command", "new"],
464 },
465 category: Category::Process,
466 subcategory: "spawn",
467 risk: Risk::Critical,
468 description: "Execute subprocess and capture output",
469 },
470 Authority {
471 pattern: AuthorityPattern::MethodWithContext {
472 method: "spawn",
473 requires_path: &["Command", "new"],
474 },
475 category: Category::Process,
476 subcategory: "spawn",
477 risk: Risk::Critical,
478 description: "Spawn subprocess",
479 },
480 Authority {
481 pattern: AuthorityPattern::MethodWithContext {
482 method: "status",
483 requires_path: &["Command", "new"],
484 },
485 category: Category::Process,
486 subcategory: "spawn",
487 risk: Risk::Critical,
488 description: "Execute subprocess and get exit status",
489 },
490 ]
491}
492
493#[cfg(test)]
494mod tests {
495 use super::*;
496
497 #[test]
498 fn registry_is_not_empty() {
499 let reg = build_registry();
500 assert!(reg.len() > 30);
501 }
502
503 #[test]
504 fn all_categories_represented() {
505 let reg = build_registry();
506 let cats: std::collections::HashSet<_> = reg.iter().map(|a| &a.category).collect();
507 assert!(cats.contains(&Category::Fs));
508 assert!(cats.contains(&Category::Net));
509 assert!(cats.contains(&Category::Env));
510 assert!(cats.contains(&Category::Process));
511 }
512
513 #[test]
514 fn risk_ordering() {
515 assert!(Risk::Low < Risk::Medium);
516 assert!(Risk::Medium < Risk::High);
517 assert!(Risk::High < Risk::Critical);
518 }
519}