Lesson 4 of 9 · 22m
SAST: Static Analysis That Doesn't Cry Wolf
Static analysis is the workhorse of shift-left and its biggest noise source — the job isn't running Semgrep, it's curating what it's allowed to say.
A fresh Semgrep run on a mature codebase will cheerfully report thousands of "findings," most of them technically correct and practically irrelevant. Run it once, present the number as an accomplishment, and you've done the easy half. The real work is the tuning loop that turns that firehose into ten findings a sprint that people actually fix.
What SAST actually does
Static analysis pattern-matches your source against known-bad shapes: string-concatenated SQL, eval, innerHTML fed by user data, weak crypto, dangerous deserialization. The better tools (Semgrep, CodeQL) do interprocedural dataflow — they can follow a value from req.query into a db.query sink across a few files.
What it's good at:
- "Never do X" rules:
eval(userInput),child_process.execwith concatenation,document.write, MD5. - Framework misuse: missing CSRF middleware,
DEBUG = Truein a Django settings file,verify=Falseon a TLS request. - Custom rules for your codebase — the underused superpower. Your ORM's footguns, your internal auth helper called wrong.
What it's bad at:
- Business-logic flaws. No scanner finds "user can refund other users' orders."
- Anything assembled at runtime: config composed at deploy, reflection, dynamic
require. DAST and review cover that side.
A custom rule is five lines of YAML
# .semgrep/no-sql-concat.yaml
rules:
- id: sql-concat-query
patterns:
- pattern: db.query($A + $B, ...)
- pattern-not: db.query("..." + "...", ...)
message: "Use parameterized queries — see security/sql.md"
severity: ERROR
languages: [javascript]semgrep scan --config .semgrep/ --config p/javascript --error--error makes findings exit non-zero — that's your gate. p/javascript pulls the community ruleset; the local directory adds house rules. A rule named after your own codebase's conventions outperforms a generic pack in signal-to-noise every time.
The tuning loop that works
- Run full-repo, advisory only. Read the top 20 findings by hand.
- Disable rule families that are pure noise for your stack — a Java deserialization rule in a Node shop earns nobody anything.
- Triage the rest: real bugs get tickets, accepted ones get
// nosemgrep: rule-id -- reason. - Only then enable blocking — and only on the rules you've vetted.
Field notes
nosemgrepsuppressions need a justification in the comment and get reviewed like code — they're an audit trail, not an escape hatch.- Language coverage is the hidden gotcha: a "multi-language" tool may have deep rules for Java and three for Go. Check rule counts for your stack before buying.
- Diff-aware scanning (
semgrep ci) is what belongs in PRs; full-repo scans are scheduled work.