Why we chose AST over regex for code scanning
Most early security scanners work by searching source code for strings that look dangerous. The logic is intuitive: if you want to find SQL injection vulnerabilities, search for patterns like query + userInput or `SELECT * FROM ${table}`. If you want to find eval misuse, search for eval(. This approach is fast to implement, fast to run, and catches a real subset of vulnerabilities. It is also the approach that produces the most false positives — findings that look like vulnerabilities in text but are not vulnerabilities in context — and misses the largest number of real issues. Understanding why illuminates a core problem in security tooling and explains why the best static analysis systems do something fundamentally different.
Regex pattern matching treats source code as text. It finds sequences of characters that match a pattern, with no understanding of what those characters mean in context. This creates two categories of failure. False positives occur when the pattern matches text that is not actually a vulnerability: a comment explaining how SQL injection works triggers the SQL injection rule; a test file that includes an eval call to verify input rejection triggers the eval rule; a variable named evaluateExpression that contains no actual eval call triggers a rule checking for the string eval. Every false positive erodes trust in the scanner — developers who see enough incorrect findings learn to ignore findings generally, including the real ones. False negatives are the inverse problem: the pattern misses a real vulnerability because it is expressed in a way the regex did not anticipate. SQL injection through an ORM that builds queries in a non-obvious way. An eval equivalent implemented with a different function name. A server-side request forgery vector that assembles its URL across multiple lines.
Abstract Syntax Tree analysis takes a different approach. Rather than reading your source code as text, an AST parser reads it as the compiler does: it builds a structured representation of your code's logical components — functions, variable assignments, function calls, expressions, control flow — and then reasons about that structure. An AST-based SQL injection check does not search for the string query + userInput. It identifies database query calls, traces the data flow from the call's arguments back to their sources, and flags the call when a data flow path reaches user-controlled input without passing through a sanitization function. This is structurally incapable of matching comments or variable names — it is analyzing program logic, not text patterns.
The practical impact on false positive rate is significant. A regex-based scanner checking for eval usage will flag eval() with no arguments, eval('literal string')that is semantically safe, every occurrence of the word "eval" in comments and string literals, and functions named evaluate or evaluation. An AST-based scanner checks whether the argument to eval is derived from user-controlled input — and only flags the cases where it is. The result is findings that are almost always real issues rather than pattern-matching coincidences. For a developer reviewing scan results, this difference is decisive: a scanner that produces mostly real findings gets used; a scanner that produces mostly noise gets disabled.
Sayver's code scanner uses AST-informed analysis to keep the false positive rate low enough to be actionable. The SQL injection rule checks for string concatenation involving user-provided values in query positions, not the presence of SQL keywords in the file. The eval rule requires a non-trivial argument — specifically, one that could be user-derived — rather than flagging every occurrence of evalin the codebase. The SSRF rule traces whether user-controlled input reaches the host portion of a URL in a fetch or HTTP call, not whether the words "fetch" and "req" appear near each other. This tuning comes from the same principle as AST analysis: understanding what the code is doing, not just what it contains.
The honest limitation of any static analysis, including AST-based approaches, is that it reasons about code structure rather than runtime behavior. It cannot track data through a database, follow a value through an external API call, or detect vulnerabilities that only manifest under specific runtime conditions. For those classes of vulnerability, dynamic analysis — testing your running application — is necessary. This is why Sayver runs a site scanner against your live app and a runtime monitor on your production traffic in addition to the code scanner. Static and dynamic analysis are complementary, not competing approaches. The code scanner finds what is wrong in your source before it is deployed; the site scanner and runtime monitor find what is wrong in your running system after it is.