Справочник

Пошаговая настройка GitLab CI

Как подключить ScanSuite к GitLab: токен, переменные, раннер и шаблон, разбор результатов, проверка merge request и готовые рецепты.

Задание GitLab запускает CI-клиент ScanSuite из образа appsec4u/scansuite-ci:1. Клиент собирает в архив файлы под контролем Git, отправляет их на сервер ScanSuite, запускает сканирование и ждёт результата. Сканирует сам сервер, так что раннеру не нужны ни сканеры, ни много памяти. После сканирования клиент проверяет результат по порогу качества (quality gate): по умолчанию сборку останавливает любая уязвимость уровня High или Critical и любой секрет, которого у продукта раньше не было. Затем клиент сохраняет отчёты и завершает работу:

ИтогЧто вы получаете
Задание прошлоБлокирующих уязвимостей нет. Найденное всё равно сохраняется в ScanSuite.
Задание завершилось с ошибкойВ журнале перечислены все блокирующие уязвимости и секреты. На вкладке Tests конвейера и в merge request они показаны как проваленные тесты.
Отчётыscansuite-junit.xml (отчёт о тестах для GitLab), scansuite.json (сводка) и scansuite.sarif. GitLab хранит их как артефакты задания 30 дней.

Что понадобится

ЧтоТребования
ScanSuiteКоманда на сервере ScanSuite Teams, в которой у вас роль «Администратор команды»: она нужна, чтобы завести сервисный аккаунт.
ПродуктПродукт ScanSuite, в который будут попадать уязвимости. Его можно создать заранее на странице «Продукты» или поручить это первому запуску конвейера (см. «Рецепты»).
GitLabРоль Maintainer в проекте: без неё не изменить настройки CI/CD и правила слияния.
РаннерРаннер GitLab с исполнителем Docker, который выполняет задания без тегов, может скачать образ appsec4u/scansuite-ci:1 с Docker Hub и видит сервер ScanSuite по HTTPS. Подойдут и общие раннеры GitLab.com, если сервер доступен из интернета.

Шаг 1. Выпустите токен в ScanSuite

Конвейер обращается к ScanSuite не с паролем сотрудника, а с токеном сервисного аккаунта. Такой аккаунт принадлежит команде и не перестанет работать, когда кто-то из неё уйдёт.

  1. 01
    Откройте страницу «Команды»

    В боковом меню, в разделе «Администрирование», выберите «Команды» и перейдите на вкладку «Люди». Нужная карточка — «Автоматизация и API-токены».

  2. 02
    Заведите сервисный аккаунт

    В поле «Новый сервисный аккаунт» укажите имя, например gitlab-ci, в поле «Роль» выберите «Оператор» и нажмите «Создать». Роль по умолчанию, «Читатель», запускать сканирования не может.

  3. 03
    Выпустите токен

    У нового аккаунта нажмите «Новый токен». Задайте имя и срок действия — не больше 90 дней, — оставьте набор прав «Конвейер CI» и нажмите «Создать токен».

  4. 04
    Скопируйте токен

    Он показывается только один раз и понадобится на шаге 2. Если токен потерялся, отзовите его и выпустите новый.

Карточка «Автоматизация и API-токены» в ScanSuite
Сервисные аккаунты и их токены на вкладке «Люди»
Окно выпуска токена в ScanSuite
Выпуск токена с набором прав «Конвейер CI»

В набор «Конвейер CI» входит только то, что нужно конвейеру: запуск и остановка сканирований, просмотр сканирований, уязвимостей, продуктов и найденных учётных данных, загрузка отчётов и создание продуктов. В списке у каждого токена видны его права и срок действия. За 14 дней до истечения срока клиент начинает предупреждать об этом в журнале задания.

Шаг 2. Добавьте переменные CI/CD в GitLab

В проекте GitLab откройте Settings → CI/CD → Variables и добавьте через Add variable четыре переменные:

KeyValueВидимость и флажки
SCANSUITE_URLАдрес сервера ScanSuite, например https://scansuite.example.comVisible
SCANSUITE_TEAMКороткое имя команды. Его видно на странице «Моя учётная запись» (меню пользователя): ссылка на команду заканчивается на ?team=<короткое имя>.Visible
SCANSUITE_PRODUCTНазвание продукта — точно так, как на странице «Продукты». Вместо него можно задать номер продукта в SCANSUITE_PRODUCT_ID.Visible
SCANSUITE_TOKENТокен из шага 1.Masked, без флажка Protect variable
Панель Add variable в GitLab
SCANSUITE_TOKEN: видимость Masked, флажок Protect variable снят
Снимите флажок Protect variable у SCANSUITE_TOKEN

GitLab ставит флажок Protect variable по умолчанию. Защищённая переменная доступна только конвейерам защищённых веток и тегов, поэтому любой merge request из обычной ветки завершится ошибкой Set SCANSUITE_TOKEN ... с кодом выхода 3. Оставляйте токен защищённым, только если все конвейеры со сканированием запускаются на защищённых ветках или тегах.

Переменные CI/CD проекта в GitLab
Все четыре переменные; маскирован только токен

Переменные проекта действуют во всех заданиях и имеют приоритет над переменными из .gitlab-ci.yml. Это важно, если какое-то задание должно отправлять результаты в другой продукт: см. Монорепозиторий.

Шаг 3. Проверьте раннер

Раннеры, доступные проекту, перечислены в Settings → CI/CD → Runners. Хотя бы один из них должен быть в состоянии Online, работать с исполнителем Docker и выполнять задания без тегов. Если ваши раннеры берут только задания с тегами, добавьте тег скрытому заданию шаблона в своём .gitlab-ci.yml:

yaml
.scansuite:
  tags: [docker]
Настройки раннеров в GitLab
Общий раннер инстанса в сети и доступен проекту

Шаг 4. Подключите шаблон

Сервер ScanSuite раздаёт шаблон для GitLab CI, который всегда соответствует версии сервера. Добавьте его в файл .gitlab-ci.yml в корне репозитория (если файла нет, создайте его), указав адрес своего сервера:

.gitlab-ci.yml
include:
  - remote: 'https://scansuite.example.com/ci/scansuite.gitlab-ci.yml'

Шаблон добавляет в стадию test два задания:

ЗаданиеКогда запускаетсяЧто сканирует
scansuite-merge-requestВ каждом merge requestquick-classic (правила Semgrep, без AI) — только файлы, изменённые в merge request
scansuite-default-branchПри каждом push в основную веткуstandard-ai (AI SAST с анализом достижимости, поиск секретов с проверкой через AI, проверка зависимостей) — весь репозиторий

Закоммитьте файл в основную ветку — этот push и запустит первый конвейер.

Если в конвейере уже объявлены стадии, оставьте их и добавьте к ним test: задания шаблона работают в этой стадии. Если подключать файл по внешней ссылке не хочется, скопируйте содержимое шаблона по тому же адресу в свой репозиторий. AI SAST считает подключение внешнего файла без фиксации версии уязвимостью уровня Medium.

Шаг 5. Посмотрите результат в GitLab

Откройте Build → Pipelines и выберите последний конвейер. В тестовом прогоне задание основной ветки работало три минуты и, как и ожидалось на таком приложении, завершилось с ошибкой:

Граф конвейера в GitLab
Тестовый конвейер: задание шаблона, классическое сканирование и задания из рецептов DAST и сканирования образа

Откройте задание с ошибкой. В конце журнала написано, что нашло сканирование и почему задание не прошло:

Журнал задания ScanSuite в GitLab
Конец журнала: блокирующие уязвимости, итог проверки и код выхода
Строка журналаСмысл
Scan jfzyghvb (#95) Queued … Finished after 160sИдентификатор сканирования: по нему сканирование можно найти в ScanSuite.
Open findings in this scan: Critical 1, High 2, …Сколько уязвимостей нашло сканирование.
BLOCKING [Critical] OS command injection -- app.py::host (Reachable)Уязвимость, из-за которой проверка не пройдена: файл, место в коде и причина. По строке на каждую.
BLOCKING [Secret] github_token -- …/settings.py#L3Блокирующий секрет и ссылка на файл в GitLab.
Quality gate FAILED: 3 blocking finding(s), 1 blocking secret(s)Итог проверки.
Exit code 1: the quality gate failed …Причина ошибки. Ниже GitLab всегда пишет exit code 1, а настоящий код выхода — в этой строке (см. «Если что-то пошло не так»).

На вкладке Tests конвейера каждая блокирующая уязвимость показана как проваленный тест, с разбивкой по заданиям. Отчёты JSON и SARIF можно скачать на странице задания, в блоке Job artifacts: кнопки Download и Browse.

Вкладка Tests конвейера в GitLab
Вкладка Tests: каждая блокирующая уязвимость — проваленный тест

Шаг 6. Найдите уязвимости в ScanSuite

Сканирование из конвейера ничем не отличается от обычного сканирования продукта. Оно появляется в «Истории сканирований» под именем архива проекта, например scansuite-ci-e2e.zip. На странице сканирования видно, что оно изменило, а по кнопке «Показать уязвимости» открывается список с уровнем, местом в коде и достижимостью. Разбирайте уязвимости как обычно. Порог учитывает только уязвимости в статусах Open и In Progress, так что уязвимость, закрытая как ложное срабатывание или принятый риск, следующий конвейер уже не остановит.

Страница сканирования в ScanSuite
Страница сканирования, запущенного из конвейера
Уязвимости в ScanSuite с фильтром по сканированию
Уязвимости, найденные этим сканированием

Каждая уязвимость ссылается на файл и строку в GitLab на том коммите, который сканировался: клиент передаёт адрес репозитория и коммит вместе с архивом.

Проверка merge request

Задание с ошибкой не даст слить изменения, только если это включено в проекте: Settings → Merge requests → Merge checks → Pipelines must succeed.

Настройка Merge checks в GitLab
С этой настройкой непройденное сканирование блокирует слияние

Тогда merge request показывает блокирующие уязвимости как проваленные тесты, а слияние становится доступно только после успешного конвейера:

Merge request в GitLab с проваленными тестами ScanSuite
Merge request, заблокированный проверкой quick-ai

Сканирование для merge request стоит выбирать внимательно. В тесте ветка добавила pickle.loads для данных из запроса и eval для параметра запроса:

СканированиеРезультат
quick-classic (по умолчанию в шаблоне)Прошло примерно за минуту: правила не сработали ни на одной из этих строк.
quick-aiПримерно за 30 секунд нашло две уязвимости Critical — небезопасную десериализацию и выполнение кода через eval — и остановило merge request.

Если в команде подключён AI, проверяйте merge request набором quick-ai. Оба набора сканируют только изменённые файлы. Поиску секретов и проверке зависимостей нужен весь репозиторий, поэтому они выполняются на основной ветке.

.gitlab-ci.yml
include:
  - remote: 'https://scansuite.example.com/ci/scansuite.gitlab-ci.yml'

scansuite-merge-request:
  variables:
    SCANSUITE_PROFILE: quick-ai

Если merge request затрагивает только исключённые файлы (например, при SCANSUITE_EXTRA_ARGS: --exclude docs/*), задание сразу проходит: сканировать нечего.

Какой набор и когда запускать

КогдаБез AIС AIСколько заняло в тесте
Merge requestquick-classicquick-aiОколо минуты
Основная веткаstandard-classicstandard-ai3–4 минуты
По ночам или перед релизомfull-classicfull-aifull-ai: от 7 до 25 минут, на большой кодовой базе намного дольше

Набор задаётся в задании переменной SCANSUITE_PROFILE. На учебном приложении standard-ai остановил сборку из-за трёх уязвимостей (инъекций и обхода каталогов, все с подтверждённой достижимостью) и одного проверенного секрета. standard-classic нашёл 15 блокирующих уязвимостей: те же инъекции, на которые сработало сразу несколько правил Semgrep, и уязвимые версии pyyaml, flask, jinja2 и requests.

Настройка порога

Каждый параметр задаётся переменной — в задании или для всего проекта:

ПеременнаяПо умолчаниюКогда задание завершается с ошибкой
SCANSUITE_FAIL_ON_SEVERITYhighМинимальный уровень, при котором задание завершается с ошибкой. Значение none отключает проверку.
SCANSUITE_MAXСколько уязвимостей каждого уровня допустимо, например high=0,medium=10.
SCANSUITE_BLOCK_CLASSКлассы уязвимостей, которые блокируют сборку при любом уровне, например sql_injection,command_injection.
SCANSUITE_MIN_CONFIDENCEanyreachable: учитываются только уязвимости, достижимость которых подтвердил AI, и уязвимости с известным эксплойтом.
SCANSUITE_FAIL_ON_SECRETSnewnew — секреты, которых у продукта раньше не было; all — все секреты; none — секреты не учитываются.

В тесте задание с SCANSUITE_FAIL_ON_SEVERITY: critical и SCANSUITE_BLOCK_CLASS: sql_injection,command_injection,code_injection всё равно остановилось на SQL-инъекциях и внедрении команд уровня High. В журнале у них вместо High severity стоит blocked class sql_injection.

Для первого сканирования продукта все секреты новые. Поэтому задание, которое создаёт продукт, или первое сканирование уже существующего репозитория остановится на давно закоммиченных секретах. Разберите их в ScanSuite или на первое время задайте SCANSUITE_FAIL_ON_SECRETS: none.

Рецепты

Каждый рецепт — это задание, которое нужно добавить в .gitlab-ci.yml после include. Задания наследуют скрытое задание шаблона .scansuite, а вместе с ним образ, отчёты и артефакты.

DAST и сканирование образов

Эти задания запускают на более поздней стадии — после развёртывания и сборки образа. Благодаря needs: [] они выполняются, даже если статическое сканирование уже остановило сборку. Без этого GitLab их пропустит, причём именно тогда, когда они нужнее всего.

.gitlab-ci.yml
stages: [test, verify]

scansuite-dast:
  extends: .scansuite
  stage: verify
  needs: []
  variables:
    SCANSUITE_SCAN_TYPE: dast
    SCANSUITE_TARGETS: https://staging.example.com
    SCANSUITE_PROFILE: quick
    SCANSUITE_FAIL_ON_SEVERITY: critical
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE != "merge_request_event"

scansuite-image:
  extends: .scansuite
  stage: verify
  needs: []
  variables:
    SCANSUITE_SCAN_TYPE: infra
    SCANSUITE_TARGETS: registry.example.com/shop/api:$CI_COMMIT_SHORT_SHA
    SCANSUITE_EXTRA_ARGS: --scanners docker_image_scan
    SCANSUITE_FAIL_ON_SEVERITY: critical
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE != "merge_request_event"

Цели DAST и сканирования инфраструктуры должны быть разрешены команде: страница «Команды», вкладка «Сканирование», карточки «Веб-цели» и «Инфраструктурные цели». Если цели нет в списке, задание завершится с кодом 3 ещё до начала сканирования. В тесте сканирование образа python:3.8-slim нашло 9 уязвимостей уровня Critical и остановило сборку, а DAST прошёл: найденные им проблемы уровней High и Medium ниже порога critical.

Ночное полное сканирование

Создайте расписание в Build → Pipeline schedules и добавьте задание. Набор full-ai анализирует историю Git, поэтому задание скачивает её целиком.

.gitlab-ci.yml
scansuite-nightly:
  extends: .scansuite
  variables:
    SCANSUITE_PROFILE: full-ai
    GIT_DEPTH: "0"
    SCANSUITE_MAX: high=0,critical=0,medium=10
    SCANSUITE_TIMEOUT: "14400"
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
Расписания конвейеров в GitLab
Ночное расписание для основной ветки

SCANSUITE_MAX задаёт допустимое количество: ни одной уязвимости High и Critical и не больше десяти Medium. В тесте ночное сканирование нашло 2 уязвимости Critical, 3 High и 2 Medium: пять из них, High и Critical, остановили сборку, а две Medium уложились в лимит.

SCANSUITE_TIMEOUT позволяет клиенту ждать до четырёх часов, но GitLab прервёт задание раньше — по собственному таймауту. По умолчанию это час; значение видно в поле Timeout на странице задания. Для длинных сканирований добавьте в задание timeout: 4h (ограничение самого раннера при этом сохраняется).

Проверка релиза по тегу

Эта проверка строже, чем для merge request: учитываются только достижимые уязвимости, но начиная с уровня Medium, и все секреты продукта, а полный отчёт сохраняется вместе с релизом.

.gitlab-ci.yml
stages: [test, release]

scansuite-release:
  extends: .scansuite
  stage: release
  variables:
    SCANSUITE_PROFILE: standard-ai
    SCANSUITE_FAIL_ON_SEVERITY: medium
    SCANSUITE_MIN_CONFIDENCE: reachable
    SCANSUITE_FAIL_ON_SECRETS: all
    SCANSUITE_EXTRA_ARGS: --report-zip scansuite-report.zip
  artifacts:
    when: always
    paths: [scansuite-report.zip, scansuite.json]
  rules:
    - if: $CI_COMMIT_TAG

Создайте тег в Code → Tags или отправьте его в репозиторий. В тесте проверка релиза не прошла из-за шести достижимых уязвимостей уровня Medium и выше — инъекций, открытого для всего интернета доступа по SSH в main.tf и подключения внешнего файла без фиксации версии, — а также из-за закоммиченного токена GitHub: при all он учитывается, хотя прежние сканирования его уже находили. Архив с отчётом остаётся в артефактах задания:

Артефакты задания GitLab: scansuite-report.zip
Задание релиза сохраняет полный отчёт и сводку

Монорепозиторий: свой продукт для каждого сервиса

Для каждой папки сервиса запускается отдельное задание, и каждое пишет в свой продукт, который создаётся при первом запуске. Продукт передаётся ключом, а не переменной: переменная SCANSUITE_PRODUCT в задании уступит переменной проекта из шага 2, и все сервисы окажутся в одном продукте.

.gitlab-ci.yml
scansuite-services:
  extends: .scansuite
  parallel:
    matrix:
      - SERVICE: [payments, web]
  variables:
    SCANSUITE_PROFILE: quick-classic
    SCANSUITE_CHANGED_ONLY: "1"
    SCANSUITE_EXTRA_ARGS: --source-dir services/$SERVICE --product-name $SERVICE --create-product
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

GitLab запускает по заданию на сервис: scansuite-services: [payments] и scansuite-services: [web]. При первом запуске в журнале появляется строка Created product …, а затем каждый журнал начинается с названия продукта, куда уходят результаты (в тесте продукты назывались e2e-$SERVICE: Team default, product e2e-payments (#10)). В тесте merge request менял оба сервиса: задание payments остановилось на SQL-инъекции, web прошло, и merge request был заблокирован из-за одного сервиса.

Продукт создаётся при первом запуске

Для нового репозитория продукт может создать сам конвейер — заводить его в ScanSuite вручную не нужно (набор прав «Конвейер CI» это позволяет). Задайте ключи в скрытом задании, и их унаследуют все задания:

.gitlab-ci.yml
.scansuite:
  variables:
    SCANSUITE_EXTRA_ARGS: --product-name $CI_PROJECT_NAME --create-product

Первый запуск пишет в журнал Created product scansuite-ci-e2e (#12), следующие уже находят этот продукт. Если задание задаёт собственный SCANSUITE_EXTRA_ARGS, это значение заменяется, поэтому повторите в нём оба ключа. Можно и иначе: добавьте в переменные проекта SCANSUITE_CREATE_PRODUCT со значением 1 рядом с SCANSUITE_PRODUCT.

Сначала без блокировок

Пока команда привыкает к результатам, сканирование может публиковать отчёты, никогда не останавливая конвейер:

.gitlab-ci.yml
scansuite-default-branch:
  variables:
    SCANSUITE_FAIL_ON_SEVERITY: none
    SCANSUITE_FAIL_ON_SECRETS: none

Если сборка должна останавливаться на уязвимостях, но не из-за недоступности ScanSuite, вместо этого добавьте SCANSUITE_EXTRA_ARGS: --soft-fail. В тесте при недоступном сервере клиент около минуты пытался подключиться, затем записал в журнал --soft-fail: exiting 0 instead of 5, и задание прошло.

Большой репозиторий: клонирование на стороне сервера

По умолчанию раннер загружает рабочую копию на сервер. Большой репозиторий удобнее отдать серверу ScanSuite: он сам склонирует ветку, а с --mode incremental будет сканировать только то, что изменилось после его последнего сканирования этой ветки.

  1. 01
    Создайте пару ключей для ScanSuite

    Например: ssh-keygen -t ed25519 -N "" -f scansuite-deploy. Не используйте эту пару ни для чего другого.

  2. 02
    Добавьте открытый ключ в GitLab

    В проекте откройте Settings → Repository → Deploy keys → Add new key и вставьте содержимое scansuite-deploy.pub. Права на запись не нужны.

  3. 03
    Добавьте закрытый ключ в ScanSuite

    На странице «Команды» откройте вкладку «Сканирование», карточку «Доступ к репозиториям» и в поле «Ключ SSH для клонирования» вставьте содержимое scansuite-deploy. Нажмите «Сохранить», а сам файл удалите.

  4. 04
    Добавьте задание

    Укажите SSH-адрес проекта. Если SSH в GitLab работает не на порту 22, используйте адрес вида ssh:// с номером порта, как в примере ниже.

Карточка «Доступ к репозиториям» в ScanSuite
Ключ SSH команды для клонирования сохранён
.gitlab-ci.yml
scansuite-incremental:
  extends: .scansuite
  variables:
    SCANSUITE_SOURCE: git
    SCANSUITE_GIT_URL: ssh://git@gitlab.example.com:2222/$CI_PROJECT_PATH.git
    SCANSUITE_PROFILE: standard-ai
    SCANSUITE_EXTRA_ARGS: --mode incremental
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Источник в журнале выглядит так: ssh://…/scansuite-ci-e2e.git @ main (the server clones the branch head, not necessarily …). Сервер берёт ветку в том состоянии, в каком она была в момент запуска сканирования, а оно может быть новее коммита, на котором запущен конвейер. В тесте первый запуск склонировал и просканировал всю ветку (и остановился на заложенных уязвимостях), а второй записал Nothing to scan: … no new commits since successful scan hzyljnog и прошёл за 13 секунд.

При первом клонировании сервер запоминает SSH-ключ хоста Git-сервера и в дальнейшем отказывается работать, если ключ изменился. Если GitLab переустановили или перенесли и ключ хоста поменялся, клонирование будет завершаться ошибкой could not resolve the remote revision: @@@@…, пока администратор ScanSuite не удалит старый ключ из файла /var/tmp/scansuite/git-auth/known_hosts в контейнере worker.

Без шаблона

Если подключать внешний файл нельзя, напишите задание сами. Entrypoint образа нужно очистить: GitLab запускает в контейнере собственную оболочку.

.gitlab-ci.yml
scansuite:
  stage: test
  image:
    name: appsec4u/scansuite-ci:1
    entrypoint: [""]
  variables:
    GIT_DEPTH: "50"
  script:
    - scansuite-ci --profile quick-classic --changed-only --junit scansuite-junit.xml --sarif scansuite.sarif
  artifacts:
    when: always
    reports: { junit: scansuite-junit.xml }
    paths: [scansuite.sarif]
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

Отдельные AI-сканирования

Ключи --scanners и --options заменяют сканеры и параметры, заданные набором. Передавайте их через SCANSUITE_EXTRA_ARGS:

.gitlab-ci.yml
# AI SAST со всеми возможностями
scansuite-ai-sast:
  extends: .scansuite
  variables:
    GIT_DEPTH: "0"
    SCANSUITE_EXTRA_ARGS: >-
      --scanners mlsast
      --options mlsast_reachability,mlsast_security_architecture,mlsast_boundary_hunt,mlsast_git_history

# Зависимости: сборку останавливают только CVE, достижимость которых подтвердил AI
scansuite-ai-deps:
  extends: .scansuite
  variables:
    SCANSUITE_MIN_CONFIDENCE: reachable
    SCANSUITE_EXTRA_ARGS: --scanners dep_checks --options dep_checks_reachability

# Секреты: сборку останавливают только проверенные секреты, которых у продукта ещё не было
scansuite-ai-secrets:
  extends: .scansuite
  variables:
    SCANSUITE_FAIL_ON_SEVERITY: none
    SCANSUITE_FAIL_ON_SECRETS: new
    SCANSUITE_EXTRA_ARGS: --scanners secrets --options secrets_ai

В тесте проверка зависимостей прошла: в requirements.txt указаны версии с известными CVE (классическое сканирование на них остановилось), но AI не нашёл ни одной, до которой можно добраться из кода приложения. Поиск секретов тоже прошёл: единственный проверенный секрет продукт уже знал, а при new учитываются только секреты, которых у продукта раньше не было.

AI SAST со всеми возможностями просканировал учебное приложение меньше чем за четыре минуты и нашёл две уязвимости Critical и три High в app.py и в обоих сервисах. Для каждой указано место в коде, через которое до неё можно добраться.

Собственный удостоверяющий центр

Если сертификат сервера ScanSuite выпущен вашим собственным удостоверяющим центром, добавьте его сертификат в репозиторий и укажите путь к нему. Без этого клиент выводит предупреждение и работает без проверки сертификата; с SCANSUITE_STRICT_TLS: "1" он вместо этого завершится с ошибкой.

.gitlab-ci.yml
.scansuite:
  variables:
    SCANSUITE_CA_BUNDLE: ci/corp-root.pem

Если что-то пошло не так

Какой бы ни была причина, GitLab завершает задание с ошибкой строкой ERROR: Job failed: exit code 1. Настоящий код выхода — в последней строке клиента, которая начинается с [scansuite]:

Журнал задания GitLab с ошибкой конфигурации ScanSuite
Ошибка конфигурации: клиент сообщает код 3, а GitLab — exit code 1
Код выходаЗначениеЧто делать
0Проверка пройдена или сканировать нечего.—
1Проверка не пройдена: есть блокирующие уязвимости или секреты.Посмотрите строки BLOCKING или вкладку Tests, исправьте код или разберите уязвимости в ScanSuite.
2Сканирование завершилось ошибкой, было отменено или отклонено сервером, или один из сканеров не отработал.Сканер указан в журнале; подробности — на странице сканирования в ScanSuite.
3Ошибка настройки: ключ, токен, права, цель или сертификат.Что исправить, написано в строке перед кодом выхода.
4Не дождались окончания сканирования (по умолчанию ждём 2 часа).Увеличьте SCANSUITE_TIMEOUT (в секундах).
5ScanSuite недоступен.Проверьте, что раннер может подключиться к SCANSUITE_URL.
Сообщение или признакПричина и решение
Set SCANSUITE_TOKEN ...Токен не попал в задание: переменная защищена (Protected), а конвейер запущен на незащищённой ветке (см. шаг 2), или переменной нет вовсе.
Product 'X' was not found in team YЗначение SCANSUITE_PRODUCT не совпадает с названием продукта, или указана не та команда. Чтобы продукт создавался автоматически, добавьте --create-product.
The API token lacks permission(s): ...Токен выпущен без набора «Конвейер CI», или у сервисного аккаунта роль «Читатель». Выпустите новый токен.
Cannot run on this server: openvas: ...Этот сканер не настроен для вашей команды. Где его настроить, сказано в сообщении.
Уязвимости попадают не в тот продуктВ задании задана переменная SCANSUITE_PRODUCT, но переменная проекта важнее. Передайте --product-name через SCANSUITE_EXTRA_ARGS.
--changed-only: no base to compare withКонвейер запущен не для merge request, или в клоне нет целевой ветки. В этом случае сканируется весь репозиторий.
This is a shallow clone, so Git history analysis sees only ...Для full-ai и других сканирований с анализом истории Git задайте GIT_DEPTH: "0".
Задание поздней стадии пропущеноСканирование на одной из предыдущих стадий завершилось с ошибкой. Добавьте заданию needs: [].

Отмена задания отменяет и сканирование. Если отменить конвейер в GitLab, клиент успеет остановить сканирование в ScanSuite (Cancelled scan zbepcxtn (pipeline interrupted)), и забытый конвейер не будет занимать сканер. А вот если клиент сам перестал ждать (SCANSUITE_TIMEOUT, код выхода 4), сканирование продолжится — чтобы его отменить, добавьте --cancel-on-timeout.

Полное описание ключей, в том числе авторизации для DAST, параметров сканирования инфраструктуры и других систем CI, — в разделе CI/CD и автоматизация.

Проверено: 2026-10-03