Skip to main content

rippy_cli/
allowlists.rs

1use std::collections::HashSet;
2use std::sync::LazyLock;
3
4/// Commands known to be safe (read-only, no side effects).
5/// Ported from Dippy's `SIMPLE_SAFE` frozenset.
6static SIMPLE_SAFE: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
7    HashSet::from([
8        // File viewing
9        "cat",
10        "head",
11        "tail",
12        "less",
13        "more",
14        "bat",
15        "hexdump",
16        "strings",
17        "xxd",
18        "od",
19        // Compressed file viewing
20        "zcat",
21        "bzcat",
22        "xzcat",
23        "zstdcat",
24        // Binary analysis
25        "nm",
26        "objdump",
27        "readelf",
28        "ldd",
29        "otool",
30        "size",
31        "file",
32        // Directory listing
33        "ls",
34        "tree",
35        "exa",
36        "eza",
37        "lsd",
38        // File info
39        "stat",
40        "wc",
41        "du",
42        "df",
43        // Text processing (read-only)
44        "grep",
45        "rg",
46        "ag",
47        "diff",
48        "cut",
49        "tr",
50        // sort has a dedicated handler (handles -o output flag)
51        "uniq",
52        "paste",
53        "join",
54        "comm",
55        "fold",
56        "fmt",
57        "nl",
58        "column",
59        "expand",
60        "unexpand",
61        "rev",
62        "tac",
63        "shuf",
64        // Encoding/hashing
65        "base64",
66        "base32",
67        "md5sum",
68        "sha1sum",
69        "sha256sum",
70        "sha512sum",
71        "cksum",
72        "sum",
73        // Search (find, fd, env, sort, yq have dedicated handlers — not in this list)
74        "locate",
75        "which",
76        "whereis",
77        "type",
78        "whence",
79        // System info
80        "whoami",
81        "hostname",
82        "uname",
83        "id",
84        "groups",
85        "uptime",
86        "pwd",
87        "date",
88        // env has a dedicated handler (can delegate inner commands)
89        "printenv",
90        "locale",
91        // Process info
92        "ps",
93        "top",
94        "htop",
95        "lsof",
96        "vmstat",
97        "iostat",
98        "free",
99        "pgrep",
100        // Network info (read-only)
101        "ping",
102        "dig",
103        "nslookup",
104        "traceroute",
105        "tracepath",
106        "netstat",
107        "ss",
108        // ifconfig and ip have dedicated handlers
109        "host",
110        "getent",
111        // Help/docs
112        "man",
113        "info",
114        "whatis",
115        "apropos",
116        "tldr",
117        "help",
118        // Shell builtins (safe)
119        "echo",
120        "printf",
121        "true",
122        "false",
123        "test",
124        "[",
125        ":",
126        // Path manipulation
127        "basename",
128        "dirname",
129        "realpath",
130        "readlink",
131        // Math
132        "bc",
133        "expr",
134        "seq",
135        // Misc read-only
136        "tty",
137        "stty",
138        "tput",
139        "yes",
140        "sleep",
141        // Version/capabilities
142        "nproc",
143        "getconf",
144        "arch",
145        "lsb_release",
146        // Modern CLI tools
147        "jq",
148        // yq has a dedicated handler (handles -i inplace)
149        "fzf",
150        "tokei",
151        "cloc",
152        "scc",
153        "hyperfine",
154        // Encoding
155        "iconv",
156        "dos2unix",
157        "unix2dos",
158        // Disk/fs info
159        "mount",
160        "findmnt",
161        "lsblk",
162        "blkid",
163        // dmesg has a dedicated handler (clear flags)
164    ])
165});
166
167/// Commands that wrap other commands — analyze the inner command instead.
168static WRAPPER_COMMANDS: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
169    HashSet::from([
170        "time", "timeout", "nice", "strace", "ltrace", "nohup", "command", "builtin",
171    ])
172});
173
174/// Check if a command is in the simple-safe allowlist.
175#[must_use]
176pub fn is_simple_safe(cmd: &str) -> bool {
177    SIMPLE_SAFE.contains(cmd)
178}
179
180/// Check if a command is a wrapper (should analyze inner command).
181#[must_use]
182pub fn is_wrapper(cmd: &str) -> bool {
183    WRAPPER_COMMANDS.contains(cmd)
184}
185
186/// Number of commands in the simple-safe allowlist.
187#[must_use]
188pub fn simple_safe_count() -> usize {
189    SIMPLE_SAFE.len()
190}
191
192/// Number of commands in the wrapper allowlist.
193#[must_use]
194pub fn wrapper_count() -> usize {
195    WRAPPER_COMMANDS.len()
196}
197
198/// Return all simple-safe commands, sorted alphabetically.
199#[must_use]
200pub fn all_simple_safe() -> Vec<&'static str> {
201    let mut cmds: Vec<_> = SIMPLE_SAFE.iter().copied().collect();
202    cmds.sort_unstable();
203    cmds
204}
205
206/// Return all wrapper commands, sorted alphabetically.
207#[must_use]
208pub fn all_wrappers() -> Vec<&'static str> {
209    let mut cmds: Vec<_> = WRAPPER_COMMANDS.iter().copied().collect();
210    cmds.sort_unstable();
211    cmds
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217
218    #[test]
219    fn known_safe_commands() {
220        assert!(is_simple_safe("cat"));
221        assert!(is_simple_safe("ls"));
222        assert!(is_simple_safe("grep"));
223        assert!(is_simple_safe("whoami"));
224        assert!(is_simple_safe("jq"));
225    }
226
227    #[test]
228    fn unknown_commands_not_safe() {
229        assert!(!is_simple_safe("rm"));
230        assert!(!is_simple_safe("sudo"));
231        assert!(!is_simple_safe("arbitrary_command"));
232    }
233
234    #[test]
235    fn wrapper_commands() {
236        assert!(is_wrapper("time"));
237        assert!(is_wrapper("timeout"));
238        assert!(is_wrapper("nice"));
239        assert!(is_wrapper("nohup"));
240        assert!(!is_wrapper("cat"));
241    }
242}