Сканирования из конвейера и порог сборки
Запуск сканирования из задания сборки по API-токену команды и остановка сборки по его результатам.
scansuite-ci.py запускает статический анализ из задания сборки, дожидается результата и роняет сборку, если сканирование подтвердило уязвимости или секреты. Положите скрипт в репозиторий, задайте три переменные и добавьте в конвейер одну строку. Скрипт входит по токену командного API, а не по паролю, и ему не нужно ничего, кроме Python 3.8 или новее.
Клиент для конвейеров работает через командный API, поэтому ему нужен ScanSuite Teams. В редакции без команд в конвейерах используются прежние скрипты, см. «Интеграция с CI/CD».
Первоначальная настройка
- 01Создайте продукт для результатов конвейера
Запомните его ID или точное название.
- 02Создайте сервисную учётную запись
В Team settings → Service accounts, с ролью operator. Назовите её, например, ci.
- 03Выпустите для неё токен
Сроком не больше 90 дней и с правами из таблицы ниже. Токен показывается только один раз.
- 04Сохраните токен в системе CI
В маскированной или секретной переменной SCANSUITE_TOKEN. SCANSUITE_URL и SCANSUITE_TEAM (слаг команды) задайте обычными переменными.
| Право токена | Для чего |
|---|---|
| scan.execute, scan.read, finding.read | Обязательны: запуск сканирования, отслеживание хода и чтение результатов. |
| product.read | Нужно, если продукт указан по названию, а не по идентификатору. |
| credential.read | Нужно для проверки секретов, которая включена по умолчанию. |
| scan.cancel | Необязательно. С этим правом отмена конвейера останавливает и его сканирование, иначе оно продолжит работать. |
| report.read | Необязательно, нужно для --report-zip. |
Скрипт проверяет свои права до запуска и сразу называет недостающие, а не падает на полпути. Сервисные учётные записи и токены описаны в главе Команды и роли.
Откуда берётся код
| Источник | Как это работает |
|---|---|
| --source zip (по умолчанию) | Раннер упаковывает рабочую копию и отправляет её на сервер. Из Git-репозитория берутся только отслеживаемые файлы (git ls-files), поэтому артефакты сборки в архив не попадают; лишнее исключите через --exclude GLOB. Закрытые репозитории лучше сканировать именно так: кроме архива, с раннера ничего не уходит. |
| --source git --git-url URL | Сервер сам клонирует репозиторий. URL и ветку по умолчанию скрипт берёт из данных системы CI. В HTTPS-адресе не должно быть учётных данных: скрипт и сервер отклоняют такие адреса, иначе учётные данные сохранились бы вместе со сканированием и попали в ссылки на уязвимости. Закрытые репозитории клонируются по SSH с ключом репозитория команды. |
С --source git сервер сканирует последний коммит ветки на момент клонирования, а он может оказаться новее коммита, который запустил конвейер. С --source zip сканируется ровно то, что лежит на раннере.
--mode incremental (только для git) сканирует изменения с момента последнего подходящего сканирования. Если изменений нет, скрипт сообщает об этом и завершается с кодом 0. С --mode custom-scope --scope ШАБЛОН сканируются только выбранные файлы.
Порог качества
| Значение по умолчанию | Когда сборка падает |
|---|---|
| --fail-on-severity high | Роняет сборку, если сканирование обнаружило уязвимости с критичностью high и выше. Уязвимости, которые AI-проверка признала недостижимыми, не учитываются, пока не добавлен ключ --include-unreachable. Значение none отключает порог по критичности. |
| --fail-on-secrets new | Роняет сборку, если этот запуск добавил в продукт новые секреты. Значение all учитывает все секреты продукта, none отключает проверку. |
Если AI-проверка выключена, любой найденный секрет считается настоящим: его никто не оценивал. Поэтому без проверки порог по секретам становится строже, а не мягче.
Коды возврата
| Код | Значение |
|---|---|
| 0 | Порог пройден, или инкрементальному сканированию нечего было проверять. |
| 1 | Порог не пройден: уязвимости, секреты или и то и другое. |
| 2 | Сканирование завершилось ошибкой, было отменено или отклонено, например из-за лимита сканирований команды или отсутствия учётных данных. |
| 3 | Ошибка конфигурации, токена или прав. |
| 4 | Истекло время ожидания (--timeout, по умолчанию 7200 секунд). |
| 5 | ScanSuite недоступен или вернул ошибку. |
С ключом --soft-fail коды 2, 4 и 5 заменяются на 0 с предупреждением, чтобы недоступный ScanSuite не блокировал релиз. Непройденный порог (1) и ошибка настройки (3) по-прежнему роняют задание.
Отчёты
| Ключ | Что сохраняет |
|---|---|
| --junit ФАЙЛ | JUnit XML: GitLab и Jenkins покажут результаты как упавшие тесты. |
| --sarif ФАЙЛ | SARIF 2.1.0 для GitHub code scanning, Azure DevOps и IDE. |
| --summary-json ФАЙЛ | Данные сканирования, результат проверки порога, уязвимости и секреты. |
| --report-zip ФАЙЛ | Полный архив отчёта. |
API не отдаёт значение секрета, и ни в один из этих файлов оно не попадает. Конвейер видит детектор, место и короткий отпечаток, по которому секреты можно отличить друг от друга.
Задание в GitLab
scansuite:
stage: test
image: python:3.12-slim
variables:
SCANSUITE_URL: https://scansuite.example.com
SCANSUITE_TEAM: appsec
SCANSUITE_PRODUCT: my-service # либо SCANSUITE_PRODUCT_ID
script:
- python scansuite-ci.py --junit scansuite-junit.xml --summary-json scansuite.json
artifacts:
when: always
reports:
junit: scansuite-junit.xml
paths: [scansuite.json]SCANSUITE_TOKEN задаётся в маскированных переменных проекта. В образах python:*-slim нет git. Без него скрипт не может получить список отслеживаемых файлов и обходит дерево каталогов со своими исключениями по умолчанию.
Повторы и прерывания
- Повторный запуск упавшего задания не создаёт второе сканирование: скрипт узнаёт задание и подключается к уже запущенному сканированию.
- При сбое сети или перегрузке сервера скрипт сам повторяет запрос.
- Если у токена есть право
scan.cancel, отмена конвейера отменяет и сканирование. - Добавьте
--no-wait, если конвейер должен запустить сканирование и идти дальше, не дожидаясь результата.
Прежде чем переводить конвейер на --mode incremental, выполните одно полное сканирование: инкрементальное сравнивает код с последним подходящим сканированием, и без него сравнивать не с чем.
Проверено: 2026-09-20