tracevault_core/
bash_command.rs1pub fn is_standalone_tracevault_subcommand(command: &str, subcommand: &str) -> bool {
11 let trimmed = command.trim();
12
13 const COMPOUND_MARKERS: &[&str] = &[
17 ";", "|", "&", "\n", "\r", "\u{0B}", "\u{0C}", "`", "$(", ">", "<",
18 ];
19 if COMPOUND_MARKERS.iter().any(|m| trimmed.contains(m)) {
20 return false;
21 }
22
23 let mut tokens = trimmed.split_whitespace();
24 let Some(arg0) = tokens.next() else {
25 return false;
26 };
27 if arg0.contains('=') {
30 return false;
31 }
32 let prog = arg0.rsplit(['/', '\\']).next().unwrap_or(arg0);
34 if prog != "tracevault" {
35 return false;
36 }
37 tokens.next() == Some(subcommand)
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44
45 #[test]
46 fn matches_bare_invocation() {
47 assert!(is_standalone_tracevault_subcommand(
48 "tracevault verify-start",
49 "verify-start"
50 ));
51 }
52
53 #[test]
54 fn matches_with_path_and_flags() {
55 assert!(is_standalone_tracevault_subcommand(
56 "/usr/local/bin/tracevault verify-start --session-id abc",
57 "verify-start"
58 ));
59 }
60
61 #[test]
62 fn matches_with_surrounding_whitespace() {
63 assert!(is_standalone_tracevault_subcommand(
64 " tracevault verify-start ",
65 "verify-start"
66 ));
67 }
68
69 #[test]
70 fn rejects_compound_with_and() {
71 assert!(!is_standalone_tracevault_subcommand(
72 "tracevault verify-start && rm -rf /",
73 "verify-start"
74 ));
75 }
76
77 #[test]
78 fn rejects_compound_with_semicolon_and_pipe() {
79 assert!(!is_standalone_tracevault_subcommand(
80 "tracevault verify-start; evil",
81 "verify-start"
82 ));
83 assert!(!is_standalone_tracevault_subcommand(
84 "tracevault verify-start | tee log",
85 "verify-start"
86 ));
87 }
88
89 #[test]
90 fn rejects_command_substitution_and_background() {
91 assert!(!is_standalone_tracevault_subcommand(
92 "tracevault verify-start $(evil)",
93 "verify-start"
94 ));
95 assert!(!is_standalone_tracevault_subcommand(
96 "tracevault verify-start &",
97 "verify-start"
98 ));
99 assert!(!is_standalone_tracevault_subcommand(
100 "tracevault verify-start `evil`",
101 "verify-start"
102 ));
103 }
104
105 #[test]
106 fn rejects_disguised_argv0() {
107 assert!(!is_standalone_tracevault_subcommand(
108 "echo tracevault verify-start",
109 "verify-start"
110 ));
111 }
112
113 #[test]
114 fn rejects_wrong_subcommand() {
115 assert!(!is_standalone_tracevault_subcommand(
116 "tracevault check",
117 "verify-start"
118 ));
119 }
120
121 #[test]
122 fn rejects_newline_compound() {
123 assert!(!is_standalone_tracevault_subcommand(
124 "tracevault verify-start\nrm -rf /",
125 "verify-start"
126 ));
127 }
128
129 #[test]
130 fn rejects_env_assignment_prefix_bypass() {
131 assert!(!is_standalone_tracevault_subcommand(
133 "EVIL=/tracevault verify-start",
134 "verify-start"
135 ));
136 assert!(!is_standalone_tracevault_subcommand(
137 "X=/tracevault evil-cmd",
138 "verify-start"
139 ));
140 assert!(!is_standalone_tracevault_subcommand(
141 "tracevault=x verify-start",
142 "verify-start"
143 ));
144 }
145
146 #[test]
147 fn rejects_vertical_tab_and_form_feed_separators() {
148 assert!(!is_standalone_tracevault_subcommand(
149 "tracevault verify-start\u{0B}evil",
150 "verify-start"
151 ));
152 assert!(!is_standalone_tracevault_subcommand(
153 "tracevault verify-start\u{0C}evil",
154 "verify-start"
155 ));
156 }
157}