CI/CD и автоматизация
Как запускать SAST, DAST и сканирование инфраструктуры из конвейеров CI/CD и скриптов и останавливать сборку, если найдены уязвимости.
scansuite-ci запускает сканирование ScanSuite из задания CI, скрипта или терминала, ждёт его окончания и завершает задание с ошибкой, если найдены подтверждённые уязвимости или секреты. Клиент умеет запускать статический анализ кода (по умолчанию), динамическое сканирование работающего веб-приложения и сканирование инфраструктуры — хостов, сетей и образов контейнеров. Сканирует сервер ScanSuite, поэтому раннеру нужен только Python 3.8 или новее либо образ контейнера с клиентом.
Клиент обращается к API команды, а оно есть только в ScanSuite Teams.
Подготовка
- 01Заведите сервисный аккаунт
На странице «Команды»: вкладка «Люди», карточка «Автоматизация и API-токены». Назначьте ему роль «Оператор» и имя, например ci.
- 02Выпустите токен
Нажмите «Новый токен», оставьте набор «Конвейер CI» и задайте срок действия до 90 дней. Скопируйте токен: он показывается только один раз.
- 03Добавьте переменные в систему CI
SCANSUITE_TOKEN — в маскированную (секретную) переменную, а SCANSUITE_URL, SCANSUITE_TEAM (слаг команды) и SCANSUITE_PRODUCT (точное название продукта) или SCANSUITE_PRODUCT_ID — в обычные.
Перед запуском клиент проверяет, хватает ли токену прав, и называет недостающие (код выхода 3). Когда до истечения срока токена остаётся меньше 14 дней, в журнале задания появляется предупреждение. Ключ --token-file ФАЙЛ читает токен из файла, а не из переменной окружения.
Как получить клиент
Скачайте его со своего сервера ScanSuite — там всегда лежит подходящая серверу версия:
curl -fsSO https://scansuite.example.com/ci/scansuite-ci.py
curl -fsS https://scansuite.example.com/ci/scansuite-ci.py.sha256 | sha256sum -c -Или возьмите образ контейнера appsec4u/scansuite-ci:1: в нём клиент запускается командой scansuite-ci, а Git уже установлен. Ключ --list-scanners показывает, какие сканеры есть на вашем сервере для каждого вида сканирования. Если на сервере есть версия клиента новее, он сообщит об этом при запуске (отключается ключом --no-version-check).
Продукты
Результаты каждого сканирования попадают в один из продуктов команды. Укажите его точное название ключом --product-name (SCANSUITE_PRODUCT) или номер ключом --product-id (SCANSUITE_PRODUCT_ID). Если продукта с таким названием нет, клиент завершается с кодом 3.
С ключом --create-product (SCANSUITE_CREATE_PRODUCT=1) продукт создаётся при первом запуске, а следующие запуски находят его сами — для нового репозитория или сервиса ничего не нужно готовить в ScanSuite заранее. Токену для этого нужно право Создавать и изменять продукты, оно входит в набор «Конвейер CI». Если команда работает с DefectDojo, продукт заводится и там, как при создании из консоли; если DefectDojo недоступен, продукт всё равно появится в ScanSuite, а в журнале будет предупреждение. Чтобы у каждого сервиса монорепозитория был свой продукт, передайте каждому заданию его каталог и продукт: --source-dir services/payments --product-name payments --create-product.
Что и когда запускать
| Событие | Что запускать |
|---|---|
| Merge request или pull request | --profile quick-classic --changed-only (без AI) или quick-ai: только изменённые файлы, несколько минут. |
| Слияние в основную ветку | --profile standard-ai, а без AI — standard-classic. |
| Раз в сутки или раз в неделю | --profile full-ai, а без AI — full-classic. |
| После развёртывания на тестовый стенд | --scan-type dast по развёрнутому приложению. |
| Релиз или изменения на серверах | --scan-type infra по серверам, на которых работает релиз. |
Статический анализ
Наборов шесть: три без AI и три с AI.
| --profile | Сканеры |
|---|---|
| quick-classic | Сигнатурный Semgrep и поиск секретов. Несколько минут. |
| standard-classic | Semgrep с полным локальным набором правил, поиск секретов и проверка зависимостей. |
| full-classic | Все классические сканеры: Semgrep (полный, быстрый и собственные правила команды), CodeQL и штатный анализатор каждого языка в репозитории, KICS для IaC, поиск секретов и проверка зависимостей. |
| quick-ai | AI Native SAST без проверки достижимости и истории Git, поиск секретов с AI-проверкой. |
| standard-ai (по умолчанию) | AI Native SAST с проверкой достижимости, поиск секретов с AI-проверкой и проверка зависимостей с AI-анализом достижимости. |
| full-ai | AI Native SAST со всеми возможностями AI (достижимость, анализ архитектуры, межфайловый поиск и история Git), поиск секретов с AI-проверкой и проверка зависимостей с AI-анализом достижимости. Работает дольше всех и расходует больше всего AI. |
Наборы с AI сообщают только о секретах, которые подтвердила AI-проверка; если для каких-то секретов проверка не сработала, клиент предупреждает, сколько их. full-ai анализирует историю Git, поэтому в конвейере нужна полная история (GitLab GIT_DEPTH: 0, GitHub fetch-depth: 0). Прежние названия работают и дальше: quick — это quick-classic, standard — standard-ai, deep — full-ai.
Ключ --scanners задаёт сканеры вместо тех, что в наборе, --add-scanners дополняет набор (например, --add-scanners snyk — для Snyk на сервере нужен его токен), а --no-ai-verification отключает AI-проверку. Ключ --list-scanners выводит наборы и все сканеры, которые есть на вашем сервере, и отмечает те, что для вашей команды запустить нельзя (OpenVAS без учётных данных, AI-сканеры без провайдера AI); если выбрать такой сканер, клиент завершится с кодом 3 ещё до начала сканирования.
С ключом --changed-only проверяются только файлы, изменённые относительно целевой ветки. В конвейерах merge request и pull request целевую ветку сообщает система CI, в остальных случаях её указывают ключом --base. Поиску секретов и проверке зависимостей нужен весь репозиторий, поэтому в этом режиме они не запускаются. Если изменений нет, задание завершается успешно.
По умолчанию раннер отправляет на сервер рабочую копию архивом: только отслеживаемые файлы из рабочего каталога CI или из --source-dir КАТАЛОГ, лишнее можно исключить ключом --exclude GLOB. Так сканируется ровно тот коммит, на котором запущен конвейер, серверу не нужен доступ к закрытому репозиторию, а ссылки в находках всё равно ведут на файлы этого коммита в вашем репозитории (адрес берётся из переменных CI или из --repository-url). С ключами --source git --git-url URL сервер клонирует репозиторий сам — закрытые репозитории по SSH ключом команды (настройки команды, вкладка «Сканирование», Ключ SSH для клонирования). Учётные данные в адресе указывать нельзя, ветку задаёт ключ --branch. --mode incremental (только для Git) сканирует лишь то, что изменилось в ветке с прошлого сканирования.
С ключами --mode custom-scope --scope ШАБЛОН (можно повторять или перечислить шаблоны в файле --scope-file ФАЙЛ) сканируются только подходящие файлы; сканеры, которые нельзя ограничить отдельными файлами, пропускаются с пометкой в журнале. Ключ --lang ЯЗЫК задаёт основной язык вместо выбранного набором, а --git-history добавляет к AI Native SAST анализ истории Git (тогда в архив попадает и .git).
Сканирование веб-приложений и инфраструктуры
Цели задаются ключом --target (его можно указать несколько раз или перечислить цели через запятую) либо переменной SCANSUITE_TARGETS. Каждая цель должна быть в списке разрешённых целей команды (страница «Команды», вкладка «Сканирование»), иначе клиент завершится с кодом 3 и сканирование не начнётся.
python scansuite-ci.py --scan-type dast --target https://staging.example.com \
--profile standard --header "Authorization: Bearer $STAGING_TOKEN" --junit scansuite-dast.xml| --profile с --scan-type dast | Сканеры |
|---|---|
| quick | Быстрое веб-сканирование и определение технологий. |
| standard (по умолчанию) | Быстрое веб-сканирование, Nuclei, определение технологий и поиск скрытых путей. |
| deep | Сбалансированное веб-сканирование, Nuclei, определение технологий, поиск скрытых путей и AI DAST. |
Чтобы сканировать закрытую часть приложения, передайте заголовки ключом --header "Имя: значение" (несколько — через |) или cookie ключом --cookie "ключ=значение" (несколько — через ;). Ключом --instructions ТЕКСТ можно подсказать AI-пентестеру (ai_pentest, для веб-приложений и инфраструктуры), на чём сосредоточиться.
Профилей для инфраструктуры нет, сканеры перечисляются явно. Обнаружение хостов, проверка патчей, OSINT и проверка образа контейнера запускаются только поодиночке, а сканеры уязвимостей можно комбинировать.
# Два хоста, все TCP-порты, даже если хосты не отвечают на ping
python scansuite-ci.py --scan-type infra --scanners openvas,nuclei \
--target 10.0.4.10,10.0.4.11 --no-ping --fail-on-severity critical
# Образ контейнера
python scansuite-ci.py --scan-type infra --scanners docker_image_scan \
--target registry.example.com/shop/api:1.4.2--ports принимает All TCP (по умолчанию), Top 1000 TCP/UDP или список портов.
Когда сборка завершается с ошибкой
В расчёт идут уязвимости из этого сканирования в статусе Open или In Progress. Уязвимости, которые AI-проверка признала недостижимыми, не учитываются, если не добавить ключ --include-unreachable. Уязвимости, найденные AI, учитываются всегда, а результаты сканеров (Semgrep, KICS, Trivy, ZAP, Wapiti, Nuclei, OpenVAS и других) — тоже, если команда не выключила их сохранение (по умолчанию оно включено): страница «Команды», вкладка «Сканирование», карточка «Уязвимости сканеров на странице „Уязвимости“».
Если какой-то из сканеров не отработал, сборка завершается с кодом 2, даже когда больше ничто её не останавливает; уязвимости от отработавших сканеров по-прежнему дают код 1.
| Ключ | Что останавливает сборку |
|---|---|
| --fail-on-severity high | Сборку останавливают уязвимости этой критичности и выше. По умолчанию high; none отключает проверку. |
| --max high=0,medium=10 | Сколько уязвимостей каждой критичности допускается. Для перечисленных уровней действует вместо --fail-on-severity. |
| --block-class sql_injection | Уязвимости этих классов останавливают сборку при любой критичности. |
| --min-confidence reachable | Сборку останавливают только уязвимости с подтверждённой достижимостью или известным эксплойтом. |
| --fail-on-secrets new | Сборку останавливают секреты, которых в продукте раньше не было (по умолчанию). all учитывает все секреты, none отключает проверку. Только для статического анализа. |
У каждого ключа есть одноимённая переменная: SCANSUITE_FAIL_ON_SEVERITY, SCANSUITE_MAX, SCANSUITE_BLOCK_CLASS, SCANSUITE_MIN_CONFIDENCE, SCANSUITE_FAIL_ON_SECRETS.
Коды возврата
| Код | Значение |
|---|---|
| 0 | Проверки пройдены или сканировать было нечего. |
| 1 | Проверки не пройдены: уязвимостей или секретов больше допустимого. |
| 2 | Сканирование завершилось с ошибкой, отменено или отклонено сервером (например, исчерпан лимит сканирований команды) либо какой-то из его сканеров не отработал. |
| 3 | Ошибка в настройках, токене или целях либо сертификат TLS не прошёл проверку. |
| 4 | Истекло время ожидания (--timeout, по умолчанию 7200 секунд). |
| 5 | ScanSuite недоступен или вернул ошибку. |
Неуспешный запуск заканчивается строкой с кодом выхода и его расшифровкой, например [scansuite] Exit code 3: configuration error: option, token, permission, target or certificate. Некоторые системы CI, в том числе GitLab, показывают для любого упавшего задания код 1 — по этой строке видно, что именно случилось.
С ключом --soft-fail коды 2, 4 и 5 заменяются на 0 с предупреждением, чтобы сбой ScanSuite не задерживал релиз.
Отчёты
| Ключ | Что сохраняется |
|---|---|
| --junit ФАЙЛ | JUnit XML: GitLab и Jenkins покажут найденное как упавшие тесты. |
| --sarif ФАЙЛ | SARIF 2.1.0 для GitHub code scanning, Azure DevOps и IDE. |
| --summary-json ФАЙЛ | Сведения о сканировании, итог проверок, уязвимости и секреты. |
| --report-zip ФАЙЛ | Полный отчёт в архиве. |
В журнале задания перечисляются все блокирующие уязвимости и секреты вместе с правилом, которое их заблокировало; ключ --max-print N ограничивает их число (по умолчанию 50).
GitLab CI
Подключите шаблон, который раздаёт ваш сервер ScanSuite, и задайте переменные из раздела Подготовка в Settings → CI/CD → Variables. Переменную SCANSUITE_TOKEN отметьте как Masked, но не Protected: защищённые переменные не попадают в конвейеры merge request из незащищённых веток, и задание завершается с кодом 3.
include:
- remote: 'https://scansuite.example.com/ci/scansuite.gitlab-ci.yml'Шаблон добавляет два задания: scansuite-merge-request проверяет изменённые файлы в каждом merge request набором quick-classic, а scansuite-default-branch сканирует основную ветку набором standard-ai. Для собственных заданий унаследуйте скрытое задание .scansuite, например чтобы запускать DAST после развёртывания. Переменные проекта в GitLab важнее переменных задания, поэтому, чтобы отправить результаты одного задания в другой продукт, передайте --product-name в SCANSUITE_EXTRA_ARGS, а не задавайте SCANSUITE_PRODUCT в самом задании.
scansuite-dast:
extends: .scansuite
stage: verify
needs: [deploy-staging]
variables:
SCANSUITE_SCAN_TYPE: dast
SCANSUITE_TARGETS: https://staging.example.comБез шаблона:
scansuite:
stage: test
image:
name: appsec4u/scansuite-ci:1
entrypoint: [""]
script:
- scansuite-ci --junit scansuite-junit.xml
artifacts:
when: always
reports:
junit: scansuite-junit.xmlПодключаете ScanSuite к GitLab впервые? В разделе Пошаговая настройка GitLab CI всё показано на скриншотах: от выпуска токена до merge request, который не удаётся слить из-за найденных уязвимостей. Там же готовые рецепты — DAST и сканирование образов, ночное сканирование, проверка релизов, монорепозитории. Эти рецепты есть и в примерах для GitLab.
Другие системы CI
В GitLab CI, GitHub Actions, Jenkins, Azure DevOps и Bitbucket Pipelines клиент сам берёт репозиторий, ветку, коммит и целевую ветку merge request из переменных окружения. В других системах передайте --base для --changed-only и уникальный для каждого запуска --idempotency-key, чтобы повторный запрос не запустил второе сканирование.
Ожидание и отмена
Клиент следит за сканированием до конца: проверяет состояние раз в 20 секунд (--poll-interval) и ждёт не дольше двух часов (--timeout, SCANSUITE_TIMEOUT; код выхода 4). С ключом --cancel-on-timeout он заодно отменяет сканирование, когда перестаёт ждать. С ключом --no-wait клиент только запускает сканирование и сразу завершается с кодом 0.
Если задание CI отменили, клиент отменяет и своё сканирование (токену нужно право Останавливать сканирования, оно входит в набор «Конвейер CI»); с ключом --no-cancel-on-interrupt сканирование продолжится. Запрос, повторённый после сетевой ошибки, подхватывает уже запущенное сканирование, а не начинает новое; если сервер перегружен, клиент повторяет запрос позже. Ключ --print-logs-on-failure выводит конец журнала сканирования, если оно завершилось с ошибкой.
Сертификаты TLS
Если сертификат сервера самоподписанный или выдан неизвестным раннеру центром сертификации, клиент выводит предупреждение и работает дальше без проверки. Чтобы сертификат проверялся, укажите сертификат центра ключом --ca-bundle; чтобы вместо этого клиент завершался с кодом 3, добавьте --strict-tls.
Проверено: 2026-10-03