Semgrep rules developers actually keep: SAST that does not get switched off

Most static analysis dies from noise. The way to keep SAST alive is to start with a tiny high-precision rule set, write rules for your own codebase's mistakes, and scan the diff rather than the repository.

The failure mode of static application security testing is always the same. The tool is enabled with every rule on, the first scan reports 4,000 findings across a codebase nobody has time to fix, the team marks them all as won't-fix, and the tool becomes a checkbox for the auditor.

Semgrep survives this better than most, not because its rules are smarter but because writing your own is realistic. A rule is a pattern that looks like the code it matches.

Scan the diff, not the repository

The single most important configuration decision: on pull requests, scan only what changed.

semgrep ci --baseline-commit "$(git merge-base origin/main HEAD)"

This turns an unpayable debt into a payable one. The 4,000 historical findings stay in a backlog you work through deliberately; the pull request only has to be clean on the twelve lines it touched. A developer who is asked to fix a problem in code they just wrote fixes it. A developer asked to fix a problem in code written in 2019 by someone who has left opens a ticket.

Scan the full repository on a schedule, separately, and route that to the security backlog.

Start with precision, not coverage

Begin with p/default or a language-specific ruleset, then delete every rule that has produced a false positive twice. A rule set of thirty rules that are right 95 percent of the time changes behaviour. A rule set of nine hundred rules that are right 40 percent of the time gets muted.

Measure it. If more than one finding in five is dismissed, the ruleset is too loose and you are spending team goodwill you will need later.

The rules worth writing yourself

Generic rules catch generic bugs. The findings that actually matter in a given codebase come from that codebase's own conventions, and those you have to write. They are short:

rules:
  - id: raw-sql-in-handler
    languages: [python]
    severity: ERROR
    message: >-
      Build queries with the repository layer (db.query) rather than string
      interpolation. See docs/db.md.
    patterns:
      - pattern-either:
          - pattern: cursor.execute(f"...")
          - pattern: cursor.execute("..." % ...)
          - pattern: cursor.execute("..." + ...)
    paths:
      include: ["app/api/**"]

The high-value custom rules are almost always about your own invariants rather than about the OWASP Top 10:

  • A new route handler registered without the authorisation decorator the rest of the codebase uses.
  • A call to the internal HTTP client that does not pass the tenant context.
  • Direct use of os.environ for a secret instead of the secrets helper that audits reads.
  • A logging call that takes a whole request or user object, which is how personal data ends up in log aggregation for seven years.
  • A new IaC resource without the tag the cost allocation depends on.

Each one of these is ten lines of YAML and catches a class of mistake that no off-the-shelf ruleset knows exists. Write them when you fix the bug: every incident postmortem should end with either a test or a Semgrep rule.

Make the message the fix

A finding that says "CWE-89: SQL Injection" teaches nobody anything. A finding that says "use db.query() — see docs/db.md, and here is the equivalent call" gets the change made in the same commit. The message field is the interface between the security team and the developer; write it as documentation, with a link.

Where it fits with the rest

Semgrep sees source. It does not see what is deployed, which library versions are actually resolved in the lockfile, or what the running application exposes. It complements dependency scanning and dynamic scanning rather than overlapping with them, and all three miss authorisation flaws.

It is also genuinely good on infrastructure as code, Dockerfiles and Kubernetes manifests, which is worth enabling even if you do not use it for application code: the pattern syntax handles YAML, and "no hostNetwork: true outside the ingress namespace" is a two-line rule.

For the auditor, and honestly

Both SOC 2 and ISO 27001 will ask whether you perform static analysis on your code, and a configured, running Semgrep with an evidenced triage process answers that cleanly — as long as "triage" is real and not a bulk dismissal. Keep the record of what was dismissed and why; that record is what turns a tool into a control. How that mapping works is worth planning before the audit rather than during it.

The honest framing for the team is different: this is not about the certificate, it is about catching in review the class of mistake you already know you make. That framing is also what keeps the rule set alive after the audit is over.

ConsultorIA

Want this done on your cloud?

A ten-day read-only assessment is free, and Skyline lets you see your estate on a map before you write to us.

Related articles