Статический анализ в CI полезен, когда он быстрый, понятный и не блокирует разработку из-за сотни старых находок. Semgrep подходит для этой роли: он ищет не по регулярным выражениям, а по синтаксическим шаблонам, не требует сборки проекта и позволяет писать свои правила на YAML. Ниже — практическая настройка для репозитория на Linux, без привязки к конкретному вендору.
Как работает Semgrep
Инструмент разбирает файл в синтаксическое дерево и сопоставляет его с шаблоном. Шаблон subprocess.call($CMD, shell=True) найдёт вызов независимо от переносов строк и имён переменных, а комментарий с похожим текстом не тронет. Метка $CMD — метапеременная, она захватывает произвольное выражение. Дополнительно доступен режим taint-анализа: источник, распространение и сток данных связываются в цепочку.
Semgrep не заменяет компилятор, тесты и ручное ревью. Он не доказывает отсутствие уязвимостей и может ошибаться. Его сильная сторона — дешёвый повторяемый поиск известных опасных паттернов на каждом коммите.
Установка и первый запуск
Для локальной работы удобен pipx: он изолирует инструмент от системного Python. В CI проще использовать официальный образ semgrep/semgrep, чтобы не настраивать окружение.
sudo apt install -y pipx
pipx install semgrep
semgrep --version
semgrep scan --config p/default --metrics off .
p/default — набор правил из реестра Semgrep. --config auto тоже работает, но отправляет метаданные о проекте и не даёт стабильного набора правил между запусками. Для воспроизводимости фиксируйте конкретные наборы и собственный файл правил в репозитории.
Свои правила
Правила хранятся в YAML. Начните с одного-двух паттернов, которые действительно важны вашему проекту: опасный вызов, отключённая проверка TLS, жёстко заданный секрет. Пример для Python:
rules:
- id: no-requests-verify-false
languages: [python]
severity: ERROR
message: "Проверка TLS отключена. Укажите verify=True или путь к CA."
patterns:
- pattern-either:
- pattern: requests.$METHOD(..., verify=False, ...)
- pattern: $SESSION.$METHOD(..., verify=False, ...)
- metavariable-regex:
metavariable: $METHOD
regex: ^(get|post|put|patch|delete|head|request)$
metadata:
cwe: "CWE-295: Improper Certificate Validation"
Полезно держать рядом тестовые примеры: файл с конструкцией, которая должна сработать, и файл с безопасным вариантом. Команда semgrep scan --test --config rules/ прогоняет такие примеры и показывает, совпали ли ожидания. Это дисциплинирует: правило без тестов быстро обрастает исключениями и перестаёт ловить то, ради чего его писали.
Проверьте синтаксис до запуска: semgrep scan --validate --config rules/. Ошибка в шаблоне обычно означает неполную конструкцию: для условия нужно писать if $X < 5: ..., а не фрагмент выражения. Тестируйте правило на файле с заведомо плохим и заведомо хорошим кодом — это дешевле, чем ловить ложные срабатывания в CI.
Запуск и форматы отчётов
Для ручной проверки хватает текстового вывода. Для CI нужен машинно-читаемый формат и код возврата, который остановит сборку. Флаг --error возвращает ненулевой код при находках, --sarif формирует отчёт для GitHub Code Scanning, а для GitLab есть отдельный формат --gitlab-sast.
semgrep scan --config p/default --config rules/ \
--error --sarif --output semgrep.sarif --metrics off
semgrep scan --config rules/ --severity ERROR --json \
--output semgrep.json --metrics off
Не включайте все правила сразу. Начните с высокого уровня достоверности, затем расширяйте набор. Если включить сотни проверок в legacy-проекте, разработчики привыкнут игнорировать красный статус — это худший исход внедрения.
Дифференциальный режим и подавление
В большом проекте старые находки не должны блокировать каждый merge request. --baseline-commit сравнивает текущее состояние с указанным коммитом и показывает только новые проблемы. В CI базовая точка — merge base ветки, а не случайный старый тег.
semgrep scan --config rules/ --baseline-commit origin/main --error
semgrep scan --config rules/ --disable-nosem --json --output all.json
# в Python-файле: # nosemgrep: no-requests-verify-false
nosemgrep отключает правило на конкретной строке или блоке. Всегда указывайте id правила и причину в комментарии рядом: через полгода никто не вспомнит, почему проверка была отключена. Периодически запускайте сканирование с --disable-nosem, чтобы увидеть накопленные исключения.
CI: минимальный рабочий пример
Ниже — job для GitLab CI. Он запускается в merge request и на основной ветке, сохраняет SARIF и не требует токена, если вы используете только локальные правила и публичные наборы. Для панели Semgrep AppSec Platform добавьте SEMGREP_APP_TOKEN в защищённые переменные проекта.
semgrep:
image: semgrep/semgrep
stage: test
variables:
SEMGREP_SEND_METRICS: "off"
script:
- semgrep scan --config p/default --config rules/
--error --sarif --output semgrep.sarif
artifacts:
when: always
paths: [semgrep.sarif]
rules:
- if: $CI_MERGE_REQUEST_IID
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
Для GitHub Actions достаточно шага с docker run --rm -v "$PWD:/src" semgrep/semgrep semgrep scan ... и загрузки SARIF через github/codeql-action/upload-sarif. В обоих случаях кэшируйте зависимости проекта, если они нужны правилам, и ограничивайте глубину клона так, чтобы baseline-коммит был доступен.
Производительность и шум
Скорость важна, но ещё важнее доверие к результату. Настройте политику реагирования: ошибка уровня ERROR блокирует merge, предупреждение создаёт задачу, INFO попадает в отчёт. Если команда не успевает разбирать находки, уменьшите набор правил, а не отключайте проверку целиком. Метрика полезной работы — не число найденных строк, а доля исправленных и подтверждённых находок.
- Сканируйте только исходники: исключите
vendor/,node_modules/, сборки и минифицированные файлы. - Ограничивайте размер файла через
--max-target-bytes, чтобы не разбирать гигантские артефакты. - Не повышайте
--jobsвыше числа ядер: переподписка потоков замедляет анализ. - Разделите быстрый блокирующий набор и полный ночной прогон.
- Для каждой находки назначьте владельца и срок разбора, иначе отчёт превратится в архив.
Чек-лист внедрения
- Правила и версия Semgrep зафиксированы в репозитории.
- Локальный запуск воспроизводит результат CI.
- Блокируются только новые находки высокого уровня.
- SARIF загружается в систему просмотра, а не теряется в логах.
- Исключения
nosemgrepимеют причину и срок пересмотра. - Есть отдельное расписание полного сканирования.
Итог
Semgrep даёт команде быстрый статический анализ без сборки и сложной инфраструктуры. Начинайте с малого набора правил, дифференциального режима и понятного отчёта, а не с тотального запрета коммитов. Тогда инструмент находит реальные проблемы, не мешает работе и постепенно становится частью инженерной культуры, а не ещё одной галочкой в пайплайне.