Introduction to Semgrep
Semgrep is a fast, open-source static analysis tool designed to find bugs, detect vulnerabilities, and enforce code standards across your codebase. Unlike traditional static analysis tools that rely on complex abstract syntax trees and slow compilation, Semgrep uses a lightweight pattern-matching approach that feels familiar to developers. You write rules that look like the code you want to find, and Semgrep does the rest.
Originally developed by Return To Corporation (r2c) and now maintained by Semgrep Inc., the tool supports over 30 programming languages including Python, JavaScript, TypeScript, Java, Go, C, C++, Ruby, PHP, and more. Its syntax-aware matching means it understands code structure, so it won't produce false positives from comments or string literals that happen to contain matching text.
Why Semgrep Matters
Security and code quality tools often fall into two camps: fast but shallow (like grep), or thorough but slow and noisy (like heavy static analyzers). Semgrep occupies a useful middle ground. It runs quickly enough to integrate into CI pipelines and pre-commit hooks, yet it understands code semantics well enough to catch real issues.
- Speed: Semgrep scans large codebases in seconds, not minutes.
- Customizability: Writing custom rules requires no compiler knowledge.
- Low false positive rate: Syntax-aware matching reduces noise.
- Language breadth: One tool covers polyglot repositories.
- Community rules: Thousands of pre-written rules are available via the Semgrep Registry.
Installing Semgrep
Semgrep can be installed through several methods. The most common approach is via pip, but Homebrew and Docker are also supported.
Installation via pip
pip install semgrep
Installation via Homebrew (macOS)
brew install semgrep
Running with Docker
docker run --rm -v "${PWD}:/src" returntocorp/semgrep semgrep scan /src
After installation, verify the tool is available on your PATH:
semgrep --version
Your First Scan
The fastest way to try Semgrep is to run it against a project using rules from the Semgrep Registry. The auto config pulls a curated set of rules based on the languages detected in your repository.
semgrep scan --config auto
This command will analyze your code, print findings to the terminal, and upload results to the Semgrep App if you have authenticated. For a local-only scan without any upload, use:
semgrep scan --config auto --json > results.json
Scanning a Specific Directory
By default, Semgrep scans the current working directory. You can target a specific path:
semgrep scan --config auto path/to/source
Understanding Semgrep Rules
Semgrep rules are written in YAML. Each rule defines a pattern to match, the languages it applies to, and optional metadata. The core idea is that the pattern looks like the code you want to find, with metavariables acting as wildcards.
A Simple Rule Example
Here is a rule that detects use of the insecure eval() function in Python:
rules:
- id: python-eval-detection
message: Avoid using eval(); it can execute arbitrary code.
severity: WARNING
languages: [python]
pattern: eval(...)
The ... is a metavariable that matches any arguments. When Semgrep encounters eval(user_input), it will flag it.
Using Metavariables
Metavariables start with $ and let you capture and reuse parts of the matched code. This rule detects password assignment to a variable and checks if it is logged:
rules:
- id: password-logged
message: "Password variable $PW is being logged"
severity: ERROR
languages: [python]
pattern-either:
- pattern: print($PW)
- pattern: logging.info($PW)
metavariable-regex:
metavariable: $PW
regex: (?i)(password|passwd|pwd)
Pattern Combinations
Semgrep supports several operators for combining patterns:
pattern: A single pattern that must match.pattern-either: Matches if any of the listed patterns match (logical OR).pattern-inside: The match must occur inside another pattern.pattern-not: Excludes matches that fit this pattern.patterns: A list of patterns that must all match (logical AND).
This example finds functions that use requests.get without setting a timeout:
rules:
- id: requests-missing-timeout
message: "requests.get() called without a timeout parameter"
severity: WARNING
languages: [python]
patterns:
- pattern: requests.get(...)
- pattern-not: requests.get(..., timeout=...)
Configuration Files
For projects that use Semgrep regularly, a configuration file keeps things organized. The default file is semgrep.yml, but you can name it anything and reference it with --config.
Project Configuration Example
rules:
- id: no-console-log
message: Remove console.log statements before production.
severity: INFO
languages: [javascript, typescript]
pattern: console.log(...)
- id: hardcoded-secret
message: Detected a hardcoded API key. Use environment variables instead.
severity: ERROR
languages: [python]
pattern: API_KEY = "..."
metadata:
category: security
cwe: "CWE-798: Use of Hard-coded Credentials"
Run the scan with:
semgrep scan --config semgrep.yml
Ignoring Files
Semgrep respects .gitignore by default. For additional exclusions, use a .semgrepignore file or the --exclude flag:
semgrep scan --config auto --exclude "tests/*" --exclude "vendor/*"
A typical .semgrepignore file:
tests/
vendor/
node_modules/
*.min.js
docs/
Integrating Semgrep into CI/CD
Running Semgrep in continuous integration ensures that issues are caught before code is merged. Most CI systems can run Semgrep with a few lines of configuration.
GitHub Actions Example
name: Semgrep Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
semgrep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: returntocorp/semgrep-action@v1
with:
config: auto
GitLab CI Example
semgrep:
image: returntocorp/semgrep
script:
- semgrep scan --config auto --json --output semgrep-results.json
artifacts:
paths:
- semgrep-results.json
Pre-commit Hook
For immediate feedback during development, add Semgrep as a pre-commit hook:
repos:
- repo: https://github.com/returntocorp/semgrep
rev: v1.50.0
hooks:
- id: semgrep
args: [--config, auto]
Using the Semgrep Registry
The Semgrep Registry hosts thousands of community-maintained rules organized by language, framework, and category. You can reference specific rule packs by name:
# Security rules for Python
semgrep scan --config p/python
# OWASP Top 10 rules
semgrep scan --config p/owasp-top-ten
# Django-specific rules
semgrep scan --config p/django
# Community-contributed rules
semgrep scan --config https://semgrep.dev/r/example-rule
You can also combine multiple configs:
semgrep scan --config p/python --config p/owasp-top-ten --config custom-rules.yml
Best Practices
Start with Auto, Then Customize
Begin with --config auto to get immediate value from curated rules. As you identify noise or gaps, gradually introduce custom rules tailored to your codebase and conventions.
Tune Rules to Reduce False Positives
False positives erode developer trust. Use pattern-not, pattern-inside, and path filters to narrow matches. If a registry rule is too noisy, copy it into your own config file and adjust it rather than disabling an entire rule pack.
Use Severity Levels Meaningfully
Semgrep supports INFO, WARNING, and ERROR severities. Reserve ERROR for issues that should block a build, WARNING for issues worth reviewing, and INFO for stylistic or informational findings. This mapping makes it straightforward to configure CI to fail only on errors.
Write Rules for Your Own Patterns
Beyond security, Semgrep excels at enforcing internal conventions. Use it to flag deprecated function calls, ensure error handling patterns are followed, or prevent use of banned libraries. Custom rules encode team knowledge directly into the development workflow.
Version Pin Your Configs
When referencing registry rules in CI, pin to specific versions or commit SHAs to avoid surprises when upstream rules change. This is especially important for production pipelines where a new rule suddenly failing the build can block deployments.
Review Findings Regularly
Schedule periodic reviews of Semgrep findings and rule sets. Codebases evolve, and rules that were useful six months ago may no longer apply. Pruning outdated rules keeps scan output relevant and actionable.
Leverage Autofix Where Possible
Semgrep supports an autofix field that lets a rule suggest a replacement. This is powerful for migration rules, such as renaming a deprecated API:
rules:
- id: deprecated-oldfunction
message: "oldFunction is deprecated, use newFunction instead"
severity: WARNING
languages: [javascript]
pattern: oldFunction($X)
fix: newFunction($X)
Apply fixes with:
semgrep scan --config autofix-rules.yml --autofix
Always review autofix changes in a diff before committing.
Conclusion
Semgrep bridges the gap between simple text search and heavyweight static analysis, giving developers a tool that is fast enough for real-time use and expressive enough to catch meaningful issues. By starting with registry rules, gradually introducing custom patterns, and integrating scans into your CI pipeline, you can build a security and code quality practice that scales with your team. The key to success with Semgrep is iteration: tune rules to match your codebase, prune what no longer applies, and treat the rule library as living documentation of your engineering standards. With thoughtful configuration and regular maintenance, Semgrep becomes a quiet but powerful ally in shipping safer, cleaner code.