SAST/DAST в CI без боли
Статический и динамический анализ в пайплайне: как не замедлить разработчиков
Два типа сканеров и почему нужны оба
SAST (Static Application Security Testing) разбирает исходный код без запуска приложения. Видит паттерны — неэкранированный пользовательский ввод, SQL-конкатенации, захардкоженные пароли (про которые подробнее в главе про управление секретами) — и помечает их как потенциальные уязвимости. DAST (Dynamic Application Security Testing) работает с уже запущенным приложением: бомбит HTTP-запросами с вредоносными payload’ами, проверяет заголовки безопасности, ищет открытые эндпоинты. SAST ловит ошибки в коде, DAST — ошибки в поведении.
Подходы дополняют друг друга. SAST находит вещи, которые DAST никогда не увидит: race condition в обработке платежей, небезопасная десериализация где-то в глубине бизнес-логики. DAST находит то, что SAST пропустит: кривые CORS-заголовки, отсутствие rate limiting, дыры в аутентификации, заметные только когда приложение работает целиком. И тот и другой превращаются в источник боли, если внедрить криво.
SAST: четыре инструмента, которые стоит знать
Semgrep задумывался как «grep для кода, который понимает синтаксис». Правила пишутся на YAML и выглядят как код, который ищется. Совпадения находятся по структуре AST, а не по тексту. Сканирование укладывается в секунды на среднем репозитории — Semgrep не строит полный граф зависимостей, а работает по паттернам. Открытый код, self-hosted, запускается локально без внешних зависимостей. В декабре 2024 Semgrep утащил часть функций под коммерческую лицензию, и в январе 2025 больше десяти вендоров форкнули Semgrep CE в проект Opengrep. Тем, кому нужен контроль над кодом инструмента, Opengrep может подойти лучше.
SonarQube Community Edition достоин отдельного слова. Открытый код, self-hosted, огромная база правил под десятки языков. Около 85% правил — про качество кода (code smells, дублирование, сложность), и только 15% — про безопасность. Это одновременно и сила, и слабость. Разработчики любят SonarQube за чистый веб-интерфейс, интеграцию с IDE через SonarLint и quality gates в CI. Для серьёзного security-анализа одного его не хватит, но в связке с Semgrep получается рабочая комбинация: SonarQube следит за качеством, Semgrep ловит уязвимости.
Trivy от Aqua Security закрывает другую плоскость: сканирует Docker-образы, зависимости (npm, pip, go modules, Maven) и IaC-конфигурации (Terraform, Kubernetes manifests) на известные CVE. Серверная часть не нужна, запуск одной командой trivy image myapp:latest или trivy fs ., результаты в терминале за секунды. Открытый код, self-hosted, живое сообщество. Если Semgrep и SonarQube роются в коде, то Trivy роется в том, от чего код зависит.
CodeQL от GitHub компилирует код в реляционную базу и позволяет писать запросы на языке QL, отслеживая поток данных через весь граф вызовов. CodeQL найдёт уязвимость, где пользовательский ввод проходит через 15 функций перед тем, как попасть в SQL-запрос. За глубину приходится платить временем: первичное сканирование занимает минуты, иногда десятки. Для публичных репозиториев бесплатно; для приватных идёт в составе GitHub Advanced Security, доступ к которому зависит от тарифа и региона.
Проблема false positives
Исследование EASE 2024 показало: CodeQL помечает 68% безопасного кода как потенциально уязвимый, Semgrep — 75%. Из каждых четырёх находок три — ложные. Картина живая: разработчик открывает pull request, видит 12 security warnings, из них 9 — мусор. Через неделю он перестаёт читать эти warnings совсем. Так выглядит alert fatigue, и так security-сканеры теряют доверие команды.
Лечится всё настройкой. Первое — стартовать с маленького набора высокоточных правил. Semgrep даёт включать правила по одному и смотреть, сколько находок генерирует каждое на конкретной кодовой базе. Включить 10 правил под основной язык, посмотреть результаты, отключить те, что дают 80% мусора. Второе — инкрементальное сканирование. Проверять только изменённые файлы в PR, а полный прогон ставить по расписанию (раз в сутки, например). Получается и быстрее, и находки касаются свежего кода, за который разработчик отвечает прямо сейчас.
Третье — тюнинг под конкретную кодовую базу. Если фреймворк автоматически экранирует вывод в шаблонах (Jinja2 с autoescape, React с JSX), все XSS-правила превратятся в сплошной шум. Их стоит выключить или написать кастомное правило, которое ловит только случаи с | safe или dangerouslySetInnerHTML.
DAST: когда и как запускать
DAST требует работающего приложения — отсюда главная сложность в CI. Развернуть приложение в тестовом окружении, дождаться готовности, запустить сканер, дождаться результатов, погасить окружение. Полное сканирование через OWASP ZAP занимает от 15 минут до нескольких часов в зависимости от размера приложения. Прогонять такое на каждый PR — затея безнадёжная.
OWASP ZAP (с 2024 года — «ZAP by Checkmarx») предлагает два режима: baseline scan и full scan. Baseline проверяет заголовки безопасности, SSL-конфигурацию и базовые уязвимости за пару минут — его можно вешать на каждый PR с preview environment. Full scan с активным crawling и fuzzing — задача для ночного пайплайна или еженедельного прогона.
Рабочий компромисс: baseline DAST scan на каждый PR при наличии preview environments (Vercel, Netlify, собственное решение), полный скан раз в неделю на staging и по расписанию перед релизами. Результаты полного прогона уезжают в Jira или GitHub Issues с назначением на security champion соответствующей команды.
Developer-friendly репортинг
Классический антипаттерн: security-сканер выплёвывает PDF-отчёт на 80 страниц, отправляет его по почте техлиду, на этом всё заканчивается. Отчёт лежит непрочитанный, потому что формат неудобный, приоритеты непонятные, а связь между находкой и конкретной строкой кода — размытая.
Находки должны приходить туда, где разработчик уже работает. Для SAST это комментарии в pull request: Semgrep оставляет inline-комментарии через CI-интеграции, SonarQube делает то же через quality gates. Оба указывают на конкретную строку и предлагают фикс прямо в CI/CD-пайплайне. Для DAST — тикеты в трекере с понятной структурой: что найдено, где воспроизвести, какой severity, ссылка на OWASP или CWE с объяснением.
Severity тоже надо калибровать. SQL-инъекция SQL-инъекции рознь: в публичном API без аутентификации это P0, в админке за VPN — P2. Когда сканер всему ставит Critical, разработчики перестают различать настоящие проблемы и шум.
Рекомендации по внедрению
Стартовать стоит с SAST в духе shift-left: интегрировать проще, работающее приложение не нужно, результаты видны в PR. Semgrep хорош для старта — открытый, self-hosted, быстрый, правила понятные, инкрементальное сканирование из коробки. 5–10 правил под основной стек, прогон по кодовой базе, отключение шумных, деплой в CI. Параллельно поднимается SonarQube Community Edition для контроля качества и Trivy для зависимостей и Docker-образов. Вся тройка живёт на собственной инфраструктуре, без внешних SaaS-зависимостей.
DAST подключается вторым этапом, когда уже есть автоматические preview environments или стабильный staging. ZAP baseline scan — разумная отправная точка: бесплатный, хорошо документированный, Docker-образ ghcr.io/zaproxy/zaproxy:stable запускается одной командой.
Измерять стоит две метрики. Mean time to remediate — сколько дней от находки до фикса. Это про процесс. False positive rate — какой процент находок разработчики помечают как ложные. Это про доверие к инструментам.