SAST: Static Application Security Testing
On this page
In plain terms
Static Application Security Testing (SAST) reads the source code you write and looks for insecure patterns, without running the program. It's the security equivalent of a proofreader who knows the ways code tends to go wrong: building a SQL query by gluing strings together, using a broken hash function, shelling out with unsanitized input.
Where SCA checks the code you reuse, SAST checks the code you author.
Why it matters
Whole categories of serious bugs are recognizable by shape: SQL injection, command injection, cross-site scripting, path traversal, hardcoded credentials, weak cryptography. Catching them while the code is still in a pull request is dramatically cheaper than after it ships. SAST is a core part of "shift-left" security (more here).
Where it fits
- Artifact: your source code.
- Stage: in the editor and on every pull request. SAST needs no deployment, so it's a natural continuous-integration (CI) gate on the diff.
How it works
Early SAST tools were simple pattern matchers (glorified grep). Modern ones parse code into an abstract syntax tree and reason about it, and the best perform taint analysis: tracking whether untrusted input (a request parameter) can flow into a dangerous sink (a database query) without being sanitized. That data-flow awareness is what separates a useful finding from noise.
Popular & reputable tools
| Tool | Notes |
|---|---|
| Semgrep | OSS, polyglot, fast; rules are readable patterns anyone can write |
| gosec | OSS, Go-specialized; type-aware, deep on Go idioms |
| Bandit | OSS, Python-specific |
| CodeQL | Powerful semantic analysis; free for open source, via GitHub |
| SonarQube, Checkmarx, Snyk Code | Commercial platforms with broad language + policy coverage |
Common pitfalls
- False positives. SAST's biggest tax. A pattern that looks injectable may be safe in context; too much noise trains people to ignore it. Tuning rules to your codebase matters.
- Language depth varies. A generic polyglot ruleset is shallower than a language-specialized analyzer (e.g. gosec for Go). Best coverage often means the right tool per language.
- It can't see runtime. Logic flaws and configuration issues that only appear when the app runs are DAST territory, not SAST.
sast control runs Semgrep over each component's source and normalizes findings to SARIF. Semgrep is the default because it needs no build step and covers many languages, which is the right trade for a control that runs on every pull request.The pitfall above is answered by running the specialist alongside the generalist rather than instead of it. gosec is type-aware and deep on Go idioms, and turning it on is
gosec: {enabled: true} under the control. Both scanners' findings arrive in one report ranked by one model, and a flaw both of them report is counted once, with the row naming the other tool. Depth on one language costs a line rather than a second pipeline. See running two scanners on one control and the control catalog.Semgrep leaves out a file over 1 MB, and abandons a rule that times out on a file, and still exits clean. Each such file is listed under Caveats as
unread rather than counted as checked. Exclude generated files with ignore:, or fail the run on them with failOnCaveats: [unread-files] in the descriptor's gate.
Keep learning
- SCA, the same idea for third-party dependencies
- DAST, finding what only appears at runtime
- Shift-left & DevSecOps, why PR-time checks pay off