How does SAST work?
A SAST tool parses code into a model it can query, usually an abstract syntax tree plus a data flow graph, then looks for paths where attacker-controlled data reaches a dangerous operation without passing through a check. No server, database or running build is required, which is why SAST can run on every pull request.
Most security-relevant SAST findings come from taint analysis, which has three parts:
- Sources: where untrusted data enters. Request parameters, headers, cookies, request bodies, file uploads, message queue payloads.
- Sinks: operations that are dangerous with untrusted data.
cursor.execute(),subprocess.run(..., shell=True),eval(), template rendering without escaping, outbound HTTP clients. - Sanitizers: functions that make data safe for a specific sink. Bound query parameters for SQL,
shlex.quote()for shell arguments, HTML escaping for templates.
The tool reports a finding when it can trace a path from a source to a sink that does not cross a sanitizer. Simpler rules skip data flow entirely and match syntax: md5( used on a password field, verify=False on a TLS client, a hardcoded key.
How far the tool can follow data matters. CodeQL's documentation distinguishes local data flow, within a single function, from global data flow, which crosses functions and object properties and costs more to compute. Semgrep's open-source engine tracks taint within a function by default; cross-function and cross-file tracking are in its commercial tier. A tool that only follows data inside one function misses a source in the controller and a sink in a helper module.
What does a SAST finding look like?
Here is a tiny Flask handler with a planted flaw:
# app/reports.py
from flask import request
from app.db import get_cursor
@app.get("/reports")
def list_reports():
sort = request.args.get("sort", "created_at") # source
query = "SELECT id, name FROM reports ORDER BY " + sort
cur = get_cursor()
cur.execute(query) # sink
return {"reports": cur.fetchall()}A taint rule that catches it, written in Semgrep's syntax as an example of the approach:
rules:
- id: flask-sqli-from-request
mode: taint
languages: [python]
severity: ERROR
message: Request data flows into a SQL query built with string concatenation
pattern-sources:
- pattern: flask.request.args.get(...)
pattern-sanitizers:
- pattern: int(...)
pattern-sinks:
- pattern: $CUR.execute($QUERY, ...)
focus-metavariable: $QUERYThe tool reports something like:
app/reports.py:10 flask-sqli-from-request
sort = request.args.get("sort", "created_at") line 7 (source)
query = "SELECT ... ORDER BY " + sort line 8 (propagates)
cur.execute(query) line 10 (sink)The fix here is an allowlist, because ORDER BY columns cannot be bound as query parameters: map sort to one of {"created_at", "name"} and reject anything else. A SAST tool will not know that an allowlist lookup is a sanitizer unless the rule says so, which leads to the next problem. The SQL injection page covers the injection side in depth.
What is SAST good at?
SAST is good at breadth and at pointing to the exact line. The OWASP Source Code Analysis Tools page lists its strengths: it scales across large codebases, runs repeatedly in CI, and reports file, line and code snippet. It is strongest on bugs with a clear source-to-sink shape:
- SQL, command, LDAP and template injection
- Cross-site scripting through unescaped output
- Path traversal from input into file operations
- Server-side request forgery from input into an HTTP client
- Weak crypto, insecure defaults and hardcoded credentials
Why does SAST produce false positives?
A false positive is a reported path that cannot be exploited, and SAST produces them because it reasons about code without knowing runtime facts. OWASP names "high numbers of false positives" as a known weakness. The usual causes:
- Unknown sanitizers. A custom
validate_sort()function that the rule does not recognize. - Unreachable paths. A source that only admins can reach, or a sink behind a feature flag that is always off.
- Type facts the tool misses. A parameter the framework has already coerced to an integer.
- Over-approximation. When the tool cannot resolve which function a dynamic call hits, it assumes the worst.
The opposite failure, false negatives, is quieter and worse. OWASP lists authentication problems, access control issues and insecure use of cryptography as areas SAST struggles with, and configuration issues are invisible to it because "they are not represented in the code." A missing requireOwner() check is not a source-to-sink path, so an authorization bug like broken object level authorization produces no finding at all.
To keep SAST useful: tune rules to your frameworks, register your own sanitizers, gate merges only on high-confidence rules, and baseline existing findings so developers only see what their change introduced. Tool accuracy varies widely. The OWASP Benchmark project is a Java and Python test suite of deliberately exploitable and deliberately safe cases that teams use to measure a tool's true and false positive rates before buying or tuning it.
SAST vs DAST vs SCA vs IAST
These four categories look at different things, so they find different bugs.
| SAST | DAST | SCA | IAST | |
|---|---|---|---|---|
| Looks at | Your source or bytecode | The running app over HTTP | Third-party dependencies | The running app from inside, via an agent |
| Needs a deployed app | No | Yes | No | Yes, plus test traffic |
| Finds | Injection, unsafe APIs, hardcoded secrets | Runtime and config flaws, injection confirmed by response | Known CVEs in packages and versions | Injection with the exact code path observed |
| Misses | Authorization, logic, config | Code paths it cannot reach, logic, authorization | Bugs in your own code | Code not exercised by tests |
| Points to a line of code | Yes | No | The manifest entry | Yes |
Dynamic application security testing confirms what is exploitable from the outside. Software composition analysis answers a different question, whether a package version has a published vulnerability, and reachability analysis narrows that to vulnerabilities your code actually calls. IAST instruments the application runtime and watches tainted data during functional tests, so its coverage is only as good as the test suite. None of the four finds a flaw that requires understanding what the application is supposed to allow.
[ Sources ]
Written by Parameter · Last reviewed

