Understanding SAST Security Testing
How SAST tools analyze source code for vulnerabilities without execution, using data flow analysis, taint tracking, and pattern matching.
Last reviewed: July 27, 2026
Introduction
Static Application Security Testing (SAST) analyzes source code, bytecode, or binary code for security vulnerabilities without executing the program. Unlike penetration testing (which requires a running application) or DAST (Dynamic Application Security Testing, which sends live attack traffic), SAST runs early in the development cycle — in your IDE or CI pipeline — finding vulnerabilities before they reach staging. Common vulnerabilities found: SQL injection, XSS, hardcoded secrets, insecure cryptography, and path traversal.
flowchart TD
A["Parse source into an Abstract Syntax Tree"] --> B["Trace tainted (external) data through the program"]
B --> C["Match against vulnerability rule patterns"]
C --> D["Flag findings in the CI pipeline"]
D --> E["Triage: true positive vs. false positive"]
How SAST Works Technically
1. Abstract Syntax Tree (AST) Analysis
SAST tools parse source code into an Abstract Syntax Tree and traverse it looking for dangerous patterns:
Source code:
query = "SELECT * FROM users WHERE id = " + user_input
AST:
BinaryExpression (+)
├── StringLiteral("SELECT * FROM users WHERE id = ")
└── Identifier(user_input) ← TAINT SOURCE (external input)
Pattern match: String concatenation into SQL query → SQL injection
2. Data Flow / Taint Analysis
More sophisticated SAST traces tainted data (data from external sources) through the program:
# SAST taint tracking example
def get_user(request):
user_id = request.GET['id'] # TAINT SOURCE: HTTP parameter
user_id = user_id.strip() # Sanitization? Not for SQL injection
query = f"SELECT * FROM users WHERE id = {user_id}" # TAINT SINK: SQL query
# SAST flags: tainted data flows from HTTP request to SQL query without parameterization
return db.execute(query)
# SAST sees this as safe (parameterized — taint doesn't reach SQL syntax)
def get_user_safe(request):
user_id = request.GET['id'] # TAINT SOURCE
result = db.execute("SELECT * FROM users WHERE id = %s", [user_id]) # TAINT SINK (safe)
return result
3. Common SAST Tools
# Semgrep — fast, rule-based, supports 20+ languages
pip install semgrep
semgrep --config=p/python-security . --output=results.json
# Bandit — Python-specific security linter
pip install bandit
bandit -r src/ -f json -o bandit-report.json
# SonarQube — comprehensive SAST platform (runs as a service)
# ESLint with security plugins (JavaScript)
npm install eslint-plugin-security
4. CI/CD Integration
# GitHub Actions: Run SAST on every PR
name: SAST Security Scan
on:
pull_request:
branches: [main, develop]
jobs:
semgrep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: semgrep/semgrep-action@v1
with:
config: >-
p/python-security
p/secrets
p/owasp-top-ten
env:
SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }}
bandit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: pip install bandit
- run: bandit -r src/ -f json -o bandit-report.json
- uses: actions/upload-artifact@v4
with:
name: bandit-report
path: bandit-report.json
5. Interpreting Results — False Positives
# Bandit finding (may be false positive in some contexts)
# [B608:hardcoded_sql_expressions] Possible SQL injection via format string
query = "SELECT * FROM {} WHERE id = {}".format(table_name, record_id)
# If table_name is from a whitelist (not user input), this is a false positive
ALLOWED_TABLES = {'users', 'products', 'orders'}
if table_name not in ALLOWED_TABLES:
raise ValueError("Invalid table name")
# Add suppression comment to acknowledge and justify
query = "SELECT * FROM {} WHERE id = %s".format(table_name) # nosec B608 — table from allowlist
Key Takeaways
- SAST analyzes source code without execution — it integrates into CI pipelines and finds vulnerabilities before deployment.
- Taint analysis tracks data from external sources (HTTP, files, DB) to dangerous sinks (SQL queries, system calls, HTML output) — the core of modern SAST.
- SAST has a false positive problem — some findings require human judgment. Use suppression comments with justifications to manage noise.
- Run SAST in CI on every PR with a high-severity block policy — low-severity findings can be reported without blocking.
- SAST complements but doesn’t replace DAST and penetration testing — some vulnerabilities (authentication logic, business logic flaws) require dynamic testing.
Historical figures, architectures, and capabilities are for informational purposes only. Not technical, professional, legal, or financial advice. Sources: Research papers, developer documentation.