Skip to main content

supercode_runtime/
policy.rs

1//! Deterministic runtime policy matching.
2
3/// Match a tool or path name against a minimal `*` wildcard pattern.
4///
5/// `*` matches any byte sequence, including empty. Every other byte matches
6/// literally. The iterative implementation is bounded and does not recurse,
7/// including for adversarial patterns with many stars.
8pub fn glob_match(pattern: &str, text: &str) -> bool {
9    let pattern = pattern.as_bytes();
10    let text = text.as_bytes();
11    let (mut pattern_index, mut text_index) = (0usize, 0usize);
12    let mut star: Option<(usize, usize)> = None;
13
14    while text_index < text.len() {
15        if pattern_index < pattern.len() && pattern[pattern_index] == b'*' {
16            star = Some((pattern_index, text_index));
17            pattern_index += 1;
18        } else if pattern_index < pattern.len() && pattern[pattern_index] == text[text_index] {
19            pattern_index += 1;
20            text_index += 1;
21        } else if let Some((star_pattern_index, star_text_index)) = star {
22            let next_text_index = star_text_index + 1;
23            star = Some((star_pattern_index, next_text_index));
24            pattern_index = star_pattern_index + 1;
25            text_index = next_text_index;
26        } else {
27            return false;
28        }
29    }
30    while pattern_index < pattern.len() && pattern[pattern_index] == b'*' {
31        pattern_index += 1;
32    }
33    pattern_index == pattern.len()
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[test]
41    fn exact_prefix_suffix_and_middle_forms() {
42        assert!(glob_match("bash", "bash"));
43        assert!(glob_match("bash*", "bash_tool"));
44        assert!(glob_match("*_write", "edit_write"));
45        assert!(glob_match("mcp__*__search", "mcp__github__search"));
46        assert!(!glob_match("bash", "bash_tool"));
47    }
48
49    #[test]
50    fn pathological_star_pattern_is_non_recursive() {
51        let pattern = "*a*a*a*a*a*a*a*a*a*a*b";
52        let text = "a".repeat(100_000);
53        assert!(!glob_match(pattern, &text));
54    }
55}