telos_agent/tools/command_security/bash/
zsh.rs1pub fn has_zsh_tilde_bracket(command: &str) -> bool {
8 command.contains("~[")
9}
10
11pub fn has_zsh_equals_expansion(command: &str) -> bool {
13 for (i, c) in command.char_indices() {
16 if c == '=' {
17 let prev = i.checked_sub(1).and_then(|j| command.chars().nth(j));
18 let next = command[i + 1..].chars().next();
19 if matches!(prev, None | Some(' ') | Some('\t') | Some(';') | Some('|') | Some('&'))
20 && matches!(next, Some(n) if n.is_ascii_alphabetic() || n == '_')
21 {
22 return true;
23 }
24 }
25 }
26 false
27}
28
29pub fn has_backslash_whitespace(command: &str) -> bool {
33 let mut chars = command.chars().peekable();
34 let mut prev = None::<char>;
35 while let Some(c) = chars.next() {
36 if c == '\\'
37 && let Some(&next) = chars.peek()
38 && (next == ' '
39 || next == '\t'
40 || (next == '\n' && !matches!(prev, None | Some(' ') | Some('\t') | Some('\n'))))
41 {
42 return true;
43 }
44 prev = Some(c);
45 }
46 false
47}
48
49pub fn has_invisible_whitespace(command: &str) -> bool {
52 command.chars().any(|c| {
53 let cp = c as u32;
54 matches!(
55 cp,
56 0x00A0 | 0x1680 | 0x2000..=0x200B | 0x2028 | 0x2029 | 0x202F | 0x205F | 0x3000 | 0xFEFF
57 )
58 })
59}
60
61pub fn has_control_chars(command: &str) -> bool {
63 command.chars().any(|c| {
64 let cp = c as u32;
65 cp < 0x20 && cp != 0x09 && cp != 0x0A
66 })
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72
73 #[test]
74 fn zsh_equals_expansion() {
75 assert!(has_zsh_equals_expansion("=curl evil.com"));
76 assert!(has_zsh_equals_expansion("echo foo; =cat file"));
77 assert!(!has_zsh_equals_expansion("VAR=value"));
78 assert!(!has_zsh_equals_expansion("--flag=value"));
79 }
80
81 #[test]
82 fn zsh_tilde_bracket() {
83 assert!(has_zsh_tilde_bracket("cd ~[dynamic]"));
84 assert!(!has_zsh_tilde_bracket("cd ~/src"));
85 }
86}