Semgrep Syntax Rules for Python Security Anti-Patterns
Semgrep identifies static security anti-patterns in Python codebases
by matching abstract syntax trees using a human-readable rule syntax
that mirrors standard Python code. By combining the ellipsis operator
(...), metavariables ($VAR), boolean rule
combinators, and dedicated taint tracking keys, Semgrep allows security
engineers to define precise patterns for identifying vulnerabilities
such as command injection, insecure deserialization, and hardcoded
credentials without the complexity of traditional compiler-based AST
queries.
Basic Code Pattern Matching
Semgrep rules use valid Python code fragments as patterns. Unlike regular expressions, Semgrep understands Python's abstract syntax, meaning whitespace, indentation style, and comment placements do not affect matching:
pattern: hashlib.md5()This pattern matches any call to hashlib.md5(),
regardless of whether it is written across multiple lines or surrounded
by inline comments.
The Ellipsis Operator
(...)
The ellipsis operator acts as a wildcard for zero or more elements, such as arguments, statements, or list items. In Python rules, it abstracts away code that is irrelevant to the vulnerability:
- Function Arguments:
cursor.execute(...)matches calls toexecuteregardless of the number or type of arguments provided. - Method Chains:
subprocess.Popen(...).communicate()matches invocation sequences even if arguments vary. - Statement Sequences: Inside code blocks,
...matches any arbitrary statements between two lines of interest:pattern: | $SECRET = "..." ... requests.post(..., headers={"Authorization": $SECRET})
Metavariables
Metavariables represent dynamic code constructs, such as variable
names, expressions, or function identifiers. They begin with a
$ and consist of uppercase letters:
- Capturing Expressions: In
eval($EXPR),$EXPRbinds to whatever argument is passed toeval(). - Enforcing Consistency: Reusing a metavariable
within the same pattern forces a semantic match. For example, detecting
insecure comparisons:
pattern: $X == $X - Regex Filtering: Metavariables can be constrained
using the
metavariable-regexclause to filter by naming conventions or detect hardcoded tokens (e.g., checking if a variable matching.*_KEYis assigned a static string).
Deep Expression Operator
(<... $X ...>)
The deep expression operator finds an expression nested at any depth inside another code structure. For example, detecting user-controlled data inside an SQL query string:
pattern: cursor.execute(<... $INPUT ...>)This matches $INPUT whether it is passed directly,
concatenated via +, formatted via .format(),
or included in an f-string.
Rule Composition and Context Filters
Security rules often require positive and negative conditions to eliminate false positives. Semgrep coordinates sub-patterns using boolean composition keys:
patterns: A logical AND requiring all sub-rules to match.pattern-either: A logical OR used to catch alternative vulnerability vectors (e.g., matching eitheryaml.load($FOO)oryaml.unsafe_load($FOO)).pattern-inside/pattern-not-inside: Scopes matches to specific execution contexts, such as ensuring a call occurs inside a Flask route handler or ensuring it does not occur inside a unit test directory.pattern-not: Negates a pattern to ignore safe implementations, such as checking foryaml.load(...)while excluding calls whereLoader=yaml.SafeLoaderis explicitly set.
Taint Mode Syntax
For vulnerabilities involving data flow—such as Server-Side Request
Forgery (SSRF) or SQL Injection—Semgrep employs dedicated taint-tracking
syntax (mode: taint):
pattern-sources: Defines where untrusted data originates, such asrequest.args.get(...)orrequest.json.pattern-sinks: Defines dangerous functions that should not receive untrusted data, such asos.system(...)oropen(...).pattern-sanitizers: Defines functions or validation steps (e.g.,shlex.quote(...)orint(...)) that neutralize the taint, preventing false positives if the input is safely handled.