Static Analysis & SAST
Static Application Security Testing (SAST) analyzes source code for vulnerabilities without executing the program. It reads your code the way a security reviewer would — looking for patterns that indicate potential flaws — but does it at machine speed across entire codebases. SAST tools catch entire categories of bugs before they reach production, making them one of the highest-leverage security investments a team can make.
What SAST Finds
SAST tools scan for known vulnerability patterns in source code. They excel at detecting structural issues that follow recognizable anti-patterns.
SQL injection patterns. SAST identifies string concatenation in database queries where user input flows directly into SQL statements.
# SAST flags this: user input concatenated into query
query = "SELECT * FROM users WHERE id = " + request.params["id"]
# SAST passes this: parameterized query
query = "SELECT * FROM users WHERE id = ?"
cursor.execute(query, [request.params["id"]])
Hardcoded secrets. API keys, passwords, and tokens embedded directly in source code are reliably detected by pattern matching.
# SAST flags these patterns
API_KEY = "sk-live-abc123def456ghi789"
db_password = "production_p@ssw0rd"
aws_secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
Insecure function calls. Functions known to be unsafe — like eval(), exec(), system(), or deprecated cryptographic functions — trigger alerts.
# SAST flags unsafe deserialization
data = pickle.loads(user_input)
# SAST flags command injection risk
os.system("ping " + hostname)
# SAST flags weak crypto
hashlib.md5(password.encode()).hexdigest()
Cross-site scripting (XSS) patterns. User input rendered in HTML without escaping is flagged as a potential XSS vector.
Path traversal. File operations using unsanitized user input that could access files outside the intended directory.
Major SAST Tools
Semgrep
Semgrep uses lightweight pattern matching with a syntax close to the target language. You write rules that look like the code you want to find.
# Semgrep rule to find SQL injection in Python
rules:
- id: sql-injection
patterns:
- pattern: |
cursor.execute("..." + $USER_INPUT)
message: "Possible SQL injection via string concatenation"
severity: ERROR
languages: [python]
Semgrep is fast, supports 30+ languages, and has a large community rule registry. It runs locally and in CI without sending code to external servers. Many teams start with Semgrep because of its low setup cost and readable rule format.
SonarQube
SonarQube provides a comprehensive code quality and security platform. It tracks issues over time, assigns them to developers, and provides a dashboard showing security posture across projects. SonarQube performs deeper dataflow analysis than simple pattern matching, tracing how tainted data moves through the application.
SonarQube is widely used in enterprise environments. It supports quality gates — thresholds that must be met before code can be merged. The commercial editions add more advanced security rules and compliance reporting.
CodeQL
CodeQL, developed by GitHub, treats code as data in a queryable database. You write queries in a SQL-like language to find patterns across the entire codebase, including interprocedural flows.
# CodeQL query to find open redirect vulnerabilities
from DataFlow::PathNode source, DataFlow::PathNode sink
where
source.getNode() instanceof RemoteFlowSource and
sink.getNode() instanceof RedirectSink
select sink, source, sink, "Open redirect from $@.", source, "user input"
CodeQL runs automatically on GitHub repositories through code scanning. It excels at finding complex vulnerabilities that require tracking data flow across multiple functions and files.
What SAST Misses
Understanding SAST limitations is as important as knowing its strengths.
Logic bugs. SAST cannot determine whether your business logic is correct. If your authorization check allows users to access resources they should not, but the code is syntactically valid, SAST will not flag it.
Runtime behavior. Issues that only manifest during execution — race conditions, timing attacks, misconfigured infrastructure — are invisible to static analysis.
Authentication flaws. A broken authentication flow that technically uses all the right functions but combines them incorrectly will pass SAST checks.
Configuration issues. SAST analyzes code, not deployment configurations. A perfectly secure application deployed with debug mode enabled or default credentials is beyond SAST's reach.
Context-dependent vulnerabilities. Some code is safe in one context and dangerous in another. SAST tools often lack the context to distinguish between the two.
Integrating SAST into CI
The highest-value SAST deployment runs on every pull request, blocking merges when critical vulnerabilities are found.
# GitHub Actions example with Semgrep
name: Security Scan
on: [pull_request]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/owasp-top-ten
env:
SEMGREP_RULES: p/security-audit p/owasp-top-ten
Fail the build on critical findings. Configure severity thresholds so that critical and high-severity findings block the merge. Medium and low findings can be tracked but should not block development velocity.
Scan incrementally. Only scan changed files on pull requests. Run full scans on a schedule (nightly or weekly) to catch issues across the entire codebase.
Keep rules updated. SAST tools regularly add new rules for newly discovered vulnerability patterns. Update your rule sets at least monthly.
The False Positive Problem
False positives are the single biggest challenge with SAST. A tool that flags 500 issues, 480 of which are harmless, trains developers to ignore all findings. This is worse than having no tool at all.
Tune aggressively. Start with a small, high-confidence rule set and expand over time. It is better to catch 10 real issues than to report 10 real issues buried in 200 false positives.
Suppress intentionally. When a finding is a false positive, suppress it with an inline comment that explains why. This prevents it from reappearing and documents the security decision.
# This is safe because user_id comes from the authenticated session, not user input
# nosemgrep: sql-injection
cursor.execute(f"SELECT * FROM orders WHERE user_id = {session.user_id}")
Track suppression rates. If more than 30% of findings are being suppressed, your rules need tuning. High suppression rates erode trust in the tool.
Assign triage responsibility. Someone on the team must own the process of reviewing new findings, suppressing false positives, and escalating real issues. Without an owner, the findings pile up and become noise.
Real-World Example
In 2021, a major financial services company integrated Semgrep into their CI pipeline across 200 repositories. In the first month, they found 47 hardcoded API keys in production code, 12 SQL injection vulnerabilities, and 8 instances of insecure deserialization. The total remediation cost was a fraction of what a single data breach would have cost. They started with just three rule categories and expanded to 15 over six months as the team built confidence in the tooling.
Common Pitfalls
- Running SAST without tuning. Default rule sets produce too many false positives. Teams lose trust and start ignoring results entirely.
- Only running SAST in CI, never locally. Developers should be able to run SAST locally before pushing code. This shortens the feedback loop from minutes to seconds.
- Treating SAST as a complete security solution. SAST is one layer. It must be combined with dependency scanning, DAST, and manual review for comprehensive coverage.
- Not updating rules. Vulnerability patterns evolve. Running year-old rules means missing recently discovered attack vectors.
- Blocking on all findings regardless of severity. Blocking merges on informational findings frustrates developers. Reserve build-breaking for critical and high severity only.
Key Takeaways
- SAST scans source code for vulnerability patterns without executing the program.
- It excels at finding SQL injection, hardcoded secrets, insecure function calls, and XSS patterns.
- It cannot find logic bugs, runtime issues, or context-dependent vulnerabilities.
- Semgrep, SonarQube, and CodeQL are the leading tools, each with different strengths.
- False positives are the biggest SAST challenge — tune rules aggressively and suppress intentionally.
- Integrate SAST into CI, fail builds on critical findings, and run incrementally on pull requests.
- SAST is one layer of a defense-in-depth testing strategy, not a standalone solution.