Static Application Security Testing reads your source for vulnerable patterns — injection, weak crypto, dangerous sinks — without running it. Wire Semgrep into CI, upload findings to code scanning, and tune the noise.
SAST analyzes source code (or bytecode) for insecure patterns without executing it: user input flowing into a SQL string, a command built from a request, use of a broken hash, a disabled TLS check. Because it needs no running app, it runs fast on every PR and points to the exact file and line — ideal early feedback. Its weakness is context: it cannot always tell whether a path is truly reachable, so it produces false positives.
SAST strengths SAST blind spots
- runs on the raw code, per PR - false positives (can't see runtime
- exact file:line to fix reachability / real data flow)
- catches known-bad patterns: - false negatives (logic/authz bugs
injection, weak crypto, it can't model)
hardcoded secrets, unsafe - needs tuning per codebase
deserialization, path
traversal, SSRF sinks
Pair it with DAST (lesson 5): SAST reads the code, DAST attacks
the running app. Neither alone is enough.Semgrep is a fast, open-source SAST tool with curated rulesets per language and framework. Its rules read like the code they match, so you can understand and extend them. Run the relevant rulesets on every pull request and output SARIF — the standard format GitHub and other tools consume.
# job: sast
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Semgrep
uses: returntocorp/semgrep-action@v1
with:
config: >-
p/default
p/security-audit
p/owasp-top-ten
generateSarif: "1"
- name: Upload findings to GitHub code scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarifThe highest-value SAST rules are the ones specific to your codebase: an internal function that must never take user input, a deprecated crypto helper, a banned API. Semgrep rules match on code structure, not brittle regex, so they catch the pattern across formatting variations. Ban your own known-dangerous patterns and the whole team stops repeating them.
# .semgrep/no-raw-sql.yml — ban string-built SQL in our codebase
rules:
- id: no-string-formatted-sql
languages: [python]
severity: ERROR
message: >
Build SQL with parameters, never string formatting.
Use db.execute(query, params). This is SQL injection.
patterns:
- pattern-either:
- pattern: cursor.execute(f"...")
- pattern: cursor.execute("..." % ...)
- pattern: cursor.execute("..." + ...)An untuned SAST tool floods the PR with findings and trains everyone to ignore it. Control the signal: fail the build only on new, high-confidence, high-severity findings; baseline the existing backlog so old debt does not block new work; and give developers a reviewed way to suppress a genuine false positive with a reason.
# fail the PR only on NEW high-severity issues, not the whole backlog
sast:
steps:
- uses: returntocorp/semgrep-action@v1
with:
config: p/security-audit
env:
# only diff-aware findings on the PR branch
SEMGREP_BASELINE_REF: origin/main
# suppress a reviewed false positive inline (with a reason):
# user_input = request.args["q"] # nosemgrep: rule-id reason: constant allowlist above
# governance: suppressions are code -> they show up in review.