Skip to main content

agentshield/parser/python/
patterns.rs

1use once_cell::sync::Lazy;
2use regex::Regex;
3
4// Dangerous subprocess/exec functions
5pub(crate) static SUBPROCESS_PATTERNS: Lazy<Vec<&str>> = Lazy::new(|| {
6    vec![
7        "subprocess.run",
8        "subprocess.call",
9        "subprocess.check_call",
10        "subprocess.check_output",
11        "subprocess.Popen",
12        "os.system",
13        "os.popen",
14        "os.exec",
15        "os.execv",
16        "os.execve",
17        "os.execvp",
18    ]
19});
20
21// GitPython's `repo.git.*` methods are dynamic dispatchers that execute
22// `git <method> ...` as shell commands. We match the `.git.` segment.
23pub(crate) static GITPYTHON_RE: Lazy<Regex> = Lazy::new(|| {
24    Regex::new(r"(?m)(\w+)\.git\.(\w+)\s*\(([^)]*)\)").expect("static regex pattern is valid")
25});
26
27pub(crate) static NETWORK_PATTERNS: Lazy<Vec<&str>> = Lazy::new(|| {
28    vec![
29        "requests.get",
30        "requests.post",
31        "requests.put",
32        "requests.patch",
33        "requests.delete",
34        "requests.head",
35        "requests.request",
36        "urllib.request.urlopen",
37        "httpx.get",
38        "httpx.post",
39        "httpx.put",
40        // httpx.AsyncClient and aiohttp.ClientSession are tracked via
41        // HTTP_CLIENT_CTX_RE + HTTP_CLIENT_METHODS instead, so their actual
42        // method calls (client.get, session.post) are detected as network ops.
43    ]
44});
45
46// HTTP method names used on client variables (e.g. `client.get(url)` where
47// `client` was bound from `httpx.AsyncClient()` or `aiohttp.ClientSession()`).
48// Checked separately from NETWORK_PATTERNS because the caller object is a
49// variable, not a known module.
50pub(crate) static HTTP_CLIENT_METHODS: Lazy<Vec<&str>> = Lazy::new(|| {
51    vec![
52        "get", "post", "put", "patch", "delete", "head", "options", "request", "fetch", "send",
53    ]
54});
55
56// Regex to detect async context managers that produce HTTP clients.
57// Matches: `async with httpx.AsyncClient(...) as <name>:`
58//          `async with aiohttp.ClientSession(...) as <name>:`
59pub(crate) static HTTP_CLIENT_CTX_RE: Lazy<Regex> = Lazy::new(|| {
60    Regex::new(
61        r"(?m)async\s+with\s+(?:\w+\.)*(?:AsyncClient|ClientSession)\s*\([^)]*\)\s+as\s+(\w+)",
62    )
63    .expect("static regex pattern is valid")
64});
65
66pub(crate) static DYNAMIC_EXEC_PATTERNS: Lazy<Vec<&str>> =
67    Lazy::new(|| vec!["eval", "exec", "compile", "__import__"]);
68
69pub(crate) static FILE_READ_PATTERNS: Lazy<Vec<&str>> = Lazy::new(|| vec!["open", "pathlib.Path"]);
70
71// Regex to find function calls with arguments: func_name(args)
72pub(crate) static CALL_RE: Lazy<Regex> = Lazy::new(|| {
73    Regex::new(r"(?m)(\w+(?:\.\w+)*)\s*\(([^)]*)\)").expect("static regex pattern is valid")
74});
75
76// Regex to find the start of a multi-line call: func_name( with no closing )
77// Captures the function name so we can match it against patterns, then look
78// ahead to the next line(s) for the first argument.
79pub(crate) static PARTIAL_CALL_RE: Lazy<Regex> =
80    Lazy::new(|| Regex::new(r"(\w+(?:\.\w+)*)\s*\(\s*$").expect("static regex pattern is valid"));
81
82// Regex to find os.environ / os.getenv patterns
83pub(crate) static ENV_ACCESS_RE: Lazy<Regex> = Lazy::new(|| {
84    Regex::new(
85        r#"(?m)os\.(?:environ\s*(?:\[\s*["']([^"']+)["']\s*\]|\.get\s*\(\s*["']([^"']+)["'])|getenv\s*\(\s*["']([^"']+)["']\s*\))"#,
86    )
87    .expect("static regex pattern is valid")
88});
89
90// Regex to find function definitions and their parameters
91pub(crate) static FUNC_DEF_RE: Lazy<Regex> = Lazy::new(|| {
92    Regex::new(r"(?m)^\s*(?:async\s+)?def\s+(\w+)\s*\(([^)]*)\)")
93        .expect("static regex pattern is valid")
94});
95
96// Sanitizer assignment: valid_path = validate_path(x) or valid_path = await validate_path(x)
97pub(crate) static SANITIZER_ASSIGN_RE: Lazy<Regex> = Lazy::new(|| {
98    Regex::new(r"(\w+)\s*=\s*(?:await\s+)?(\w+(?:\.\w+)*)\s*\(")
99        .expect("static regex pattern is valid")
100});