SE

Semgrep

Fast, customizable static analysis for many languages

Developer Tools & Git ★ 16.7k stars Medium setup LGPL-2.1

Semgrep is an open-source static analysis tool that finds bugs and security issues using lightweight, customizable rules. The CLI engine is open source and runs entirely in self-hosted CI.

Key features

  • Pattern-based code search
  • 30+ language support
  • Custom rule writing
  • Security rule registry

Pros & cons

Strengths

  • Fast pattern-based rules
  • Many languages supported
  • Easy custom rules

Trade-offs

  • Cross-file analysis limited
  • Advanced features paid

Semgrep replaces

Last reviewed Aug 26, 2026 · 835 words

The first run is one command and the trap is inside it: semgrep --config auto scans a repository with a bundle of registry rules chosen for its languages and, unless you add --metrics=off, reports which rules fired back to semgrep.dev. Add the flag, or name your rulesets explicitly, and what remains is a static analyser that covers 30-plus languages, finishes a mid-sized repository in well under a minute on a 512 MB box, and uses a rule format you can learn over lunch. It is LGPL-2.1, written in OCaml, and at 16,399 stars it is the most widely used open scanner of its kind.

A rule is a code pattern with holes in it

Semgrep matches the structure of code rather than its text, so a pattern ignores whitespace, comments and argument order where the language allows. This rule flags shell=True in the subprocess module unless the command is a plain string literal:

rules:
  - id: subprocess-shell-true-nonliteral
    languages: [python]
    severity: ERROR
    message: shell=True with a built command string is an injection risk; pass an argv list
    patterns:
      - pattern: subprocess.$FUNC(..., shell=True, ...)
      - pattern-not: subprocess.$FUNC("...", shell=True, ...)

$FUNC is a metavariable that matches any identifier, ... matches any sequence of arguments, and "..." matches any string literal. Save it under .semgrep/ in the repository and run semgrep --config .semgrep/ .. Rules are tested by writing example files with # ruleid: and # ok: comments and running semgrep --test, which is the part that turns a folder of rules into something a team trusts. Most useful rules are not security at all: "nobody calls this deprecated helper", "every handler has a timeout", "no print outside the CLI package". That is where Semgrep beats a traditional linter, because you write the check in the shape of the code you want to forbid.

The registry is broad, and its licence is not the engine's

Registry rulesets are addressed as p/name. p/ci is a curated high-confidence set, p/owasp-top-ten and the per-language packs (p/python, p/golang) go wider, and you can stack several --config flags. The engine is LGPL, but many registry rules ship under Semgrep's own rules licence, which restricts using them inside a competing commercial product. For scanning your own code in your own CI that is irrelevant; for building a scanning service to sell, read it first. If you would rather not depend on the registry at all, vendor the rule files you use into the repository and run entirely offline.

Wiring it into a self-hosted forge

The semgrep/semgrep image runs anywhere a container runs. A Woodpecker step that fails the build on any ERROR-severity finding looks like this:

steps:
  - name: semgrep
    image: semgrep/semgrep
    commands:
      - semgrep scan --config p/ci --config .semgrep/ --metrics=off --severity ERROR --error .

--error sets a non-zero exit code when findings exist; drop --severity ERROR to also fail on warnings once the backlog is clean. --sarif -o semgrep.sarif produces a report that forge UIs and other tools can ingest, and --baseline-commit main limits output to findings introduced since that commit, which is how you adopt it on a codebase with 400 existing hits without blocking everyone. The same one-liner works in a Gitea Actions job or a pre-commit hook. For a full pipeline the CI/CD category covers the runners.

Where the free engine stops

The open engine analyses one file at a time. It does single-file taint tracking (mode: taint rules that follow data from a source to a sink) and it does it well, but a value that crosses a function in another file is invisible to it. Cross-file and cross-function analysis, along with dependency and secrets scanning with their own dashboards, are the paid Pro engine and platform. Whether that gap matters depends on your code: for a monolith with thin modules the free engine catches most of what Pro would, for a heavily layered service it misses more. Pair it with gitleaks for secrets and Trivy for dependency CVEs and the free stack covers the three things most teams actually need. SonarQube is the alternative when you want quality dashboards and history; it costs 2 GB or more of RAM and a database, where Semgrep costs a container that exits.

What I'd do

Run semgrep scan --config p/ci --metrics=off locally today and look at the output. Commit a .semgrep/ folder with three rules that encode conventions your team argues about in review, with test files. Put the Woodpecker step above on every pull request with --baseline-commit until the existing findings are worked off, then remove the baseline. Add gitleaks and Trivy alongside it. Only look at Pro or SonarQube when someone asks for a trend chart, because the engine you have is not the limiting factor for a long time.

Compare Semgrep

1 head-to-head comparisons.

Similar developer tools & git apps