1use serde::Serialize;
4
5use crate::allowlists;
6
7const CRITICAL_COMMANDS: &[&str] = &[
8 "sudo",
9 "su",
10 "doas",
11 "eval",
12 "exec",
13 "source",
14 "chmod",
15 "chown",
16 "chgrp",
17 "mkfs",
18 "dd",
19 "fdisk",
20 "iptables",
21 "systemctl",
22 "service",
23];
24
25const HIGH_COMMANDS: &[&str] = &[
26 "rm", "rmdir", "mv", "cp", "install", "docker", "podman", "kubectl", "pip", "pip3", "npm",
27 "yarn", "pnpm", "gem", "curl", "wget", "ssh", "scp", "rsync", "kill", "killall", "pkill",
28 "mount", "umount",
29];
30
31const SAFE_SUBCOMMANDS: &[&str] = &[
33 "git status",
34 "git log",
35 "git diff",
36 "git show",
37 "git branch",
38 "git remote",
39 "git stash list",
40 "git tag",
41 "docker ps",
42 "docker images",
43 "docker inspect",
44 "cargo check",
45 "cargo test",
46 "cargo clippy",
47 "cargo fmt",
48 "cargo build",
49 "cargo doc",
50 "npm test",
51 "npm run",
52 "kubectl get",
53 "kubectl describe",
54];
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
58#[serde(rename_all = "lowercase")]
59pub enum RiskLevel {
60 Low,
61 Medium,
62 High,
63 Critical,
64}
65
66impl RiskLevel {
67 pub const fn as_str(self) -> &'static str {
68 match self {
69 Self::Low => "low",
70 Self::Medium => "medium",
71 Self::High => "high",
72 Self::Critical => "critical",
73 }
74 }
75}
76
77#[must_use]
79pub fn classify(group_key: &str) -> RiskLevel {
80 if SAFE_SUBCOMMANDS.contains(&group_key) {
82 return RiskLevel::Low;
83 }
84
85 let cmd = group_key.split_whitespace().next().unwrap_or("");
86
87 if CRITICAL_COMMANDS.contains(&cmd) {
88 return RiskLevel::Critical;
89 }
90 if HIGH_COMMANDS.contains(&cmd) {
91 return RiskLevel::High;
92 }
93 if allowlists::is_simple_safe(cmd) {
94 return RiskLevel::Low;
95 }
96 RiskLevel::Medium
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn critical_commands() {
105 assert_eq!(classify("sudo"), RiskLevel::Critical);
106 assert_eq!(classify("eval"), RiskLevel::Critical);
107 assert_eq!(classify("chmod"), RiskLevel::Critical);
108 }
109
110 #[test]
111 fn high_commands() {
112 assert_eq!(classify("rm"), RiskLevel::High);
113 assert_eq!(classify("docker"), RiskLevel::High);
114 assert_eq!(classify("curl"), RiskLevel::High);
115 assert_eq!(classify("npm"), RiskLevel::High);
116 }
117
118 #[test]
119 fn low_simple_safe() {
120 assert_eq!(classify("ls"), RiskLevel::Low);
121 assert_eq!(classify("cat"), RiskLevel::Low);
122 assert_eq!(classify("grep"), RiskLevel::Low);
123 }
124
125 #[test]
126 fn low_safe_subcommands() {
127 assert_eq!(classify("git status"), RiskLevel::Low);
128 assert_eq!(classify("git log"), RiskLevel::Low);
129 assert_eq!(classify("cargo test"), RiskLevel::Low);
130 assert_eq!(classify("docker ps"), RiskLevel::Low);
131 }
132
133 #[test]
134 fn medium_default() {
135 assert_eq!(classify("make"), RiskLevel::Medium);
136 assert_eq!(classify("git push"), RiskLevel::Medium);
137 assert_eq!(classify("unknown-tool"), RiskLevel::Medium);
138 }
139
140 #[test]
141 fn ordering_low_to_critical() {
142 assert!(RiskLevel::Low < RiskLevel::Medium);
143 assert!(RiskLevel::Medium < RiskLevel::High);
144 assert!(RiskLevel::High < RiskLevel::Critical);
145 }
146}