Security6 мин чтения2026-03-06

Semgrep SAST: как настроить статический анализ безопасности без боли и страданий

На одном проекте мы словили SQL-инъекцию в продакшене. Классика — параметр из запроса без санитизации ушёл в query. Код прошёл ревью у двух сеньоров. Никто не з

На одном проекте мы словили SQL-инъекцию в продакшене. Классика — параметр из запроса без санитизации ушёл в query. Код прошёл ревью у двух сеньоров. Никто не заметил. Потому что люди не замечают такое. Люди устают, люди торопятся, люди смотрят в телефон пока катится билд.

После того инцидента мы внедрили Semgrep. За неделю он нашёл ещё три подобных места. Теперь я не запускаю проект без SAST.

Что такое SAST и почему без него никак

Static Application Security Testing — это анализ кода без его запуска. Инструмент смотрит на исходники и ищет паттерны, которые могут привести к уязвимостям. В отличие от DAST, который долбит запущенное приложение запросами, SAST работает с текстом. Как линтер, только про безопасность.

Semgrep — один из инструментов SAST. С open-source версией. Написан на OCaml (да, это существует), работает быстро, не требует сборки проекта. В отличие от того же SonarQube, который надо разворачивать и кормить ресурсами, Semgrep — это бинарник. Установил, запустил, получил результат.

Главная фишка Semgrep — правила. Они читаемые. Их можно писать самому. Смотрите:

rules:
  - id: sql-injection
    patterns:
      - pattern: cursor.execute($QUERY, ...)
      - pattern-not: cursor.execute("...", ...)
    message: "Possible SQL injection — query is not a string literal"
    severity: ERROR
    languages: [python]

Правило ловит вызовы cursor.execute, где запрос — не строковый литерал. То есть переменная. Значит, потенциально пользовательский ввод. Простейший случай, но его пропускают постоянно.

Типичные уязвимости, которые ловит Semgrep

SQL-инъекции

Самый дорогой класс уязвимостей. Не по частоте — по последствиям. Утечка базы, дамп пользователей, репутационные потери.

# Плохо — классическая инъекция
def get_user(username):
    query = f"SELECT * FROM users WHERE username = '{username}'"
    return db.execute(query)

# Хорошо — параметризованный запрос
def get_user(username):
    query = "SELECT * FROM users WHERE username = ?"
    return db.execute(query, (username,))

Semgrep найдёт первый вариант. И второй пометит как безопасный. Умеет различать f-strings от обычных строк, понимает конкатенацию, видит, где переменная пришла из запроса.

Hardcoded secrets

Пароли, ключи API, токены прямо в коде. Каждый второй проект этим страдает. Особенно если проект делали "на вчера" и деплой был ручной.

# Semgrep это найдёт
API_KEY = "sk-1234567890abcdef"
DATABASE_PASSWORD = "admin123"

# И это тоже
requests.get(url, auth=("admin", "super_secret"))

Правила Semgrep знают форматы ключей AWS, GitHub, Slack, Stripe и ещё сотни сервисов. Находит даже "закодированные" в base64 пароли. Спойлер: base64 — это не шифрование.

XSS и рендеринг пользовательского контента

Актуально для веб-приложений, которые возвращают HTML. Особенно если вы используете шаблонизаторы неправильно.

# Flask, опасно
@app.route('/profile')
def profile():
    bio = request.args.get('bio')
    return render_template_string(f"<div>{bio}</div>")

# Django, тоже опасно
def view(request):
    data = request.GET.get('data')
    return HttpResponse(mark_safe(data))

render_template_string с f-string и mark_safe — это почти гарантированный XSS. Semgrep видит, что переменная пришла из request, и орёт.

Command injection

Выполнение системных команд с пользовательским вводом. Одна из самых критичных уязвимостей — можно получить RCE.

import os
import subprocess

# Катастрофа
filename = request.args.get('file')
os.system(f"cat {filename}")

# Тоже плохо
subprocess.call("grep " + pattern + " file.txt", shell=True)

Если shell=True и в команде есть пользовательский ввод — это беда. Semgrep найдёт и os.system, и subprocess с опасными паттернами.

Path traversal

Чтение файлов за пределами разрешённой директории. Классика: пользователь передаёт ../../../etc/passwd.

# Уязвимый код
@app.route('/download')
def download():
    filename = request.args.get('file')
    with open(f"/var/files/{filename}") as f:
        return f.read()

Semgrep проверит, есть ли санитизация пути. Нет? Будет замечание.

Интеграция в CI/CD pipeline

По-хорошему, SAST должен работать на каждый пул-реквест. Не после мерджа, не перед релизом, а именно на этапе ревью. Чтобы разработчик видел проблемы до того, как код попадёт в основную ветку.

Простейший вариант для GitLab CI:

semgrep:
  image: returntocorp/semgrep
  script:
    - semgrep ci --config=auto
  rules:
    - if: $CI_MERGE_REQUEST_IID
  allow_failure: false

Для GitHub Actions:

name: Semgrep
on: [pull_request]
jobs:
  semgrep:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: returntocorp/semgrep-action@v1
        with:
          config: auto

config: auto — это набор правил, которые Semgrep выбирает автоматически по языкам в проекте. Для продакшена лучше явно указать правила:

config:
  - p/security-audit
  - p/secrets
  - p/python
  - p/javascript

Правила — это YAML-файлы. Можете писать свои под специфику проекта. Например, если у вас есть внутренняя библиотека для работы с БД, можно написать правило, которое проверяет, что все запросы идут через неё, а не через сырой SQL.

Отчёты и игнорирование ложных срабатываний

Любой SAST даёт false positives. Это неизбежность. Semgrep — не исключение. Где-то он увидит инъекцию там, где её нет. Где-то не распознает санитизацию.

Для таких случаев есть аннотации:

# nosemgrep: sql-injection
query = f"SELECT * FROM {table_name}"  # table_name из конфига, не от пользователя

Или в отдельном файле .semgrepignore:

legacy/
migrations/
vendor/

Лучше не игнорировать файлы целиком, а подавлять конкретные правила. Иначе пропустите реальные проблемы.

Отчёты Semgrep можно выгружать в JSON или SARIF. SARIF — стандартный формат для security-отчётов, его понимают GitLab, GitHub, Azure DevOps. Отчёт можно залить в SonarQube или DefectDojo для агрегации.

Semgrep против конкурентов

SonarQube — классика. Мощный, но тяжёлый. Требует сервер, базу, настройку. Для маленькой команды — оверхед. Semgrep запускается за минуту.

Checkmarx — Enterprise-монстр. Дорогой, закрытый, с годовым контрактом. Если у вас миллион строк кода и отдельный security-отдел — ок. Для стартапа или mid-size — избыточно.

Snyk — хороший продукт, но триальный период заканчивается, и начинается боль. Semgrep имеет реально свободную версию с открытым кодом. Правила из Community Registry бесплатны.

CodeQL от GitHub — мощно, но сложно. Нужно строить базу данных кода, писать запросы на специальном языке. Semgrep — проще. Правила читаемые, можно написать за полчаса.

Практические рекомендации

Начинайте с готовых правил. Не пытайтесь сразу написать свои. Конфигурация auto или p/security-audit покроет 80% случаев. Остальные 20% — это специфика вашего проекта.

Добавляйте Semgrep в pre-commit хуки. Локально, до пуша. Быстрая проверка на критичные уязвимости занимает секунды. Да, это не заменит полный анализ в CI, но сэкономит время на исправление.

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/returntocorp/semgrep
    rev: v1.52.0
    hooks:
      - id: semgrep
        args: ['--config', 'auto', '--error']

Смотрите на severity. ERROR — блокируйте мердж. WARNING — можно пропустить, но оставить комментарий. INFO — просто информация.

Обучайте команду. SAST-отчёт — не приговор, а подсказка. Разработчик должен понимать, почему код помечен как опасный. Иначе будет просто ставить # nosemgrep везде.

Внедряйте постепенно. Сначала на новые проекты, потом на существующие. На легаси Semgrep найдёт сотни проблем. Это демотивирует. Лучше включить его только на новых MR, а старый код проверять отдельно.


Если не хотите настраивать Semgrep и разбираться с правилами — есть готовые решения. Distiq, например, делает автоматический code review с проверкой безопасности. Он использует похожие подходы к анализу кода, но всё уже настроено за вас. Интегрируется в GitLab, GitHub или GitVerse за пару минут, находит уязвимости и оставляет комментарии прямо в MR. Я рекомендую попробовать, если нужно быстро закрыть вопрос с безопасностью без настройки инфраструктуры.

Попробуйте Distiq для автоматического code review

AI-бот анализирует каждый MR/PR и оставляет комментарии с замечаниями. Интеграция за 2 минуты.

Попробовать бесплатно

Похожие статьи