SecurityFlow Кабинет лицензий , откроется в новой вкладке

Документация / CI/CD и API

Security checker

Обновлено 20 октября 2026

Security checker - консольная утилита asoc-gate в образе Docker для джобы CI. Она запускает через Core скан коммита в Radar, ждёт разбора отчётов и сверяет находки с порогом. Результат - код выхода: 0 пропускает пайплайн, 1 и 2 его роняют. Вместе с правилом ветки "слияние только после успешного пайплайна" это закрывает слияние кода с неразобранными уязвимостями.

Как работает

  1. Определяет репозиторий, ветку и коммит из переменных CI или из каталога .git.
  2. Находит в Core ассет-репозиторий с тем же путём group/project. Не нашёл - ошибка, код 2.
  3. Запускает аудит продукта этого ассета (POST /api/v1/ci/pipelines/run/). Остальные ассеты продукта исключаются, сканируется только этот репозиторий. Ветка передаётся как <ветка>:<sha>, если включён пин коммита.
  4. Находит созданный аудит и ждёт пайплайн Radar. В лог печатает смену статусов джоб. Пайплайн "Остановлен" или "Тайм-аут" - код 2.
  5. Ждёт, пока Core разберёт отчёты всех джоб-сканеров.
  6. Проверяет, что скан дал хоть какой-то результат. Ноль новых, известных и дублей по всем отчётам - код 2.
  7. Считает находки по режиму и порогу, печатает таблицы, пишет отчёт JUnit.
  8. Завершается с кодом 0, 1 или 2.

Сканирует Radar, а не раннер CI: Code Downloader клонирует ветку из системы контроля версий по SSH URL ассета, сканеры берутся из последовательности ассета.

Требования

Что Требование
Лицензия Core с доступом к Radar, см. Лицензирование
Radar подключён к Core, в последовательности есть Code Downloader из каталога. Он умеет переключаться на коммит из <ветка>:<sha>
Ассет репозиторий заведён в продукте Core ассетом типа "Репозиторий". Путь из SSH URL (git@gitlab.example.ru:team/payments.git → team/payments) совпадает с путём проекта в CI без учёта регистра
Пользователь Core сервисный пользователь с API-токеном, см. ниже
CI GitLab CI, раннер с исполнителем Docker или Kubernetes, который может скачать образ Security checker
Сеть раннер → Core по HTTPS, путь /api/v1/

Роль сервисного пользователя

Право роли Уровень Зачем
"Продукты" "Просмотр" поиск ассета, чтение аудита
"Уязвимости" "Изменение" чтение находок. Radar отправляет отчёты токеном этого пользователя, а приём отчёта требует "Изменение"
"Radar" "Изменение" запуск аудита, чтение пайплайна
"API токен" "Просмотр" получить свой токен в профиле
Остальные "Нет" -

Область видимости роли - продукты, чьи репозитории проверяет Security checker. Подробно - Роли и права, Пользователи.

Получение

Образ Security checker: alpine 3.21 и статический бинарь /usr/local/bin/asoc-gate, архитектуры amd64 и arm64. Тег образа - версия Security checker. Команда SecurityFlow размещает образ в реестре площадки и сообщает его путь. В примерах ниже - registry.example.ru/security-checker:<версия>.

Версия печатается первой строкой вывода: asoc-gate <версия>.

Аутентификация

Security checker обращается к Core с заголовком Authorization: Token <токен>.

  1. Создайте сервисного пользователя и роль с правами из таблицы выше.
  2. Войдите под ним и скопируйте токен: "Профиль" → "Токен аутентификации API".
  3. Сохраните токен в CI как маскированную и защищённую переменную на уровне группы или экземпляра GitLab.

Передать токен можно двумя способами:

  • ASOC_URL=https://core.example.ru и ASOC_TOKEN=<токен>;
  • одной переменной ASOC_DSN=https://<токен>@core.example.ru - токен в части пользователя URL.

Если задан ASOC_DSN, переменные ASOC_URL и ASOC_TOKEN не читаются.

Переменные окружения

Имя Обязательная По умолчанию Описание
ASOC_URL да, если нет ASOC_DSN - адрес Core: https://core.example.ru
ASOC_TOKEN да, если нет ASOC_DSN - API-токен Core
ASOC_DSN да, если нет ASOC_URL - адрес и токен одной строкой: https://<токен>@core.example.ru[:порт]
ASOC_FAIL_ON нет critical,high блокирующие уровни через запятую: info, low, medium, high, critical. none - только отчёт
ASOC_GATE_MODE нет open режим: open или new. Другое значение - ошибка, код 2
ASOC_PIN_COMMIT нет 1 1 - сканировать коммит джобы, 0 - текущее состояние ветки
ASOC_TIMEOUT нет 30m общий предел от старта: поиск аудита, ожидание пайплайна и разбора. Формат: 45m, 1h30m
ASOC_POLL нет 10s период опроса Core
ASOC_SETTLE нет 30s сколько ждать, если число разобранных отчётов перестало меняться
ASOC_JUNIT нет security-gate.xml путь отчёта JUnit. none - не писать
ASOC_JOB_SEQUENCE нет - id последовательности в Core. Не задан - последовательность ассета или по умолчанию
ASOC_REPO нет из CI путь репозитория group/project
ASOC_BRANCH нет из CI ветка
ASOC_ALLOW_EMPTY нет - 1 - скан без единого результата не считать ошибкой
ASOC_INSECURE нет - 1 - не проверять сертификат Core
ASOC_WEB_URL нет "Внешний URL приложения" из Core адрес Core для ссылок в выводе
ASOC_MAX_PAGES нет 2000 предел страниц при обходе находок, по 24 на страницу
ASOC_LIST_LIMIT нет 100 строк в таблице блокирующих находок
ASOC_LINK_MODE нет url short - ссылка под словом "link" (OSC 8). Такие ссылки понимает не каждый просмотрщик логов
ASOC_COLOR нет авто 1 или 0. Авто - цвет включён в GitLab CI и GitHub Actions

Числа в ASOC_MAX_PAGES и ASOC_LIST_LIMIT - положительные. Ошибка в любой переменной - сообщение asoc-gate: ... в stderr и код 2 без отчёта JUnit.

ASOC_INSECURE

ASOC_INSECURE=1 отключает проверку сертификата Core, и токен уходит по непроверенному соединению. Если Core работает на сертификате корпоративного центра сертификации, передайте файл с цепочкой в переменной SSL_CERT_FILE вместо отключения проверки.

Режимы и порог

Порог - уровни из ASOC_FAIL_ON. Неразобранными считаются статусы "Не обработано", "В работе" и "Назначено". Статусы "Решено", "Отклонено", "Временно принят риск" и "Постоянно принят риск" гейт не держат.

Режим Когда код 1 Для чего
open в ветке есть неразобранные находки блокирующих уровней - новые или старые держать весь долг ветки
new этот скан добавил находки блокирующих уровней постепенное внедрение: ловить новые уязвимости, не трогая старый долг
ASOC_FAIL_ON=none никогда, только отчёт пилот и сбор статистики

Как гейт считает находки:

  • new - находки репозитория, появившиеся в Core после старта гейта, с веткой джобы.
  • open - неразобранные находки репозитория, у которых в Core указана ровно ветка джобы.
  • При настройках дедупликации по умолчанию (критерий "Та же ветка" выключен) находка, совпавшая с уже известной в другой ветке, повторно не импортируется и в подсчёт ветки джобы не попадает. Поэтому долг целевой ветки, например main, не блокирует merge request в неё. См. Дедупликация.

Разобрали находку в Core - решили, отклонили или приняли риск - и следующий запуск гейта её не учитывает. Отдельно снимать блокировку не нужно.

Рекомендуемые сочетания

Режим выбирайте вместе с пином коммита. С пином Security checker передаёт в Core ветку в виде <ветка>:<sha>, и находки скана получают эту ветку. Режим new считает находки этого скана и работает с пином. Режим open считает находки, у которых указана ровно ветка джобы, поэтому сочетайте его с ASOC_PIN_COMMIT=0.

Цель Настройки
Не пропускать новые уязвимости коммита ASOC_GATE_MODE=new, пин включён (по умолчанию)
Держать весь неразобранный долг ветки ASOC_GATE_MODE=open, ASOC_PIN_COMMIT=0
Только отчёт ASOC_FAIL_ON=none

Пин коммита

При ASOC_PIN_COMMIT=1 Security checker передаёт ветку как <ветка>:<sha>, а Code Downloader после клонирования переключается на этот коммит. Вердикт относится к коммиту джобы, даже если в ветку уже запушили новые.

Пин не применяется, если:

  • коммит не определился - предупреждение commit pin: no commit sha detected;
  • <ветка>:<sha> длиннее 100 символов - предупреждение branch name too long.

В обоих случаях сканируется текущее состояние ветки.

Если Code Downloader не может получить коммит джобы, он завершается с сообщением commit <sha> is not reachable from branch <ветка>. Дальше зависит от флага "Разрешить падение" у Code Downloader:

  • выключен (рекомендуется, см. Типы джоб) - пайплайн останавливается, Security checker возвращает код 2;
  • включён - сканеры проверяют текущее состояние ветки, а не коммит джобы.

Для пайплайнов, где коммит джобы не принадлежит ветке, задайте ASOC_PIN_COMMIT=0.

Коды выхода

Код Значение Типичная причина
0 SECURITY GATE: PASS блокирующих находок нет
1 SECURITY GATE: FAIL - <число> <уровень>, ... найдены блокирующие находки
2 ошибка, вердикта нет неверная переменная; ассет не найден; ошибка HTTP при запуске; таймаут; пайплайн "Остановлен" или "Тайм-аут"; пустой скан; Core насчитал больше новых находок, чем видно по ветке

Любая ошибка - стоп: пайплайн не станет зелёным, если проверить не удалось.

Таймауты

  • ASOC_TIMEOUT (по умолчанию 30 минут) отсчитывается от старта и покрывает поиск аудита, ожидание пайплайна Radar и ожидание разбора.
  • Один запрос к Core ждёт ответа до 60 секунд. Временные ошибки HTTP в циклах ожидания пропускаются до истечения ASOC_TIMEOUT.
  • Таймаут джобы в GitLab задайте больше ASOC_TIMEOUT, иначе GitLab оборвёт джобу без отчёта.
  • Время скана - сумма времени джоб последовательности ассета плюс ожидание свободного воркера Radar.

Вывод

Лог джобы

Пример (сокращён):

asoc-gate 0.3.0  repo=team/payments  branch=feature/login  detected-from=gitlab
commit 9f1c2e7d4b5a6c8e0f1a2b3c4d5e6f7a8b9c0d1e
scan ref feature/login:9f1c2e7d4b5a6c8e0f1a2b3c4d5e6f7a8b9c0d1e (commit pinned)
10:02:11 asset #15 in product "payments" (2 other assets excluded), last audit #120, known findings up to #4810
10:02:23 audit #121 started, waiting for the auditor pipeline
10:02:23   Code Downloader              IN_PROGRESS
10:03:40   Gitleaks Scan                FINISHED
10:05:02   Semgrep                      FINISHED
10:05:14 pipeline #87 finished, waiting for the import of 2 reports

┌─────────────────────┬───────┬───────────┬────────┐
│ scanner             │ новых │ известных │ дублей │
├─────────────────────┼───────┼───────────┼────────┤
│ Gitleaks Scan       │ 0     │ 3         │ 0      │
│ Semgrep JSON Report │ 1     │ 12        │ 0      │
└─────────────────────┴───────┴───────────┴────────┘

Security Gate  режим: new
┌──────────┬───────┬───────────┐
│ severity │ новых │ блокирует │
├──────────┼───────┼───────────┤
│ CRITICAL │ 0     │ да        │
│ HIGH     │ 1     │ да        │
│ MEDIUM   │ 0     │ нет       │
│ LOW      │ 0     │ нет       │
│ INFO     │ 0     │ нет       │
└──────────┴───────┴───────────┘

Гейт не пропускает, находок: 1
┌──────────┬─────────────────────┬─────────────────┬───────────────────────────────────────┐
│ severity │ scanner             │ location        │ ссылка                                │
├──────────┼─────────────────────┼─────────────────┼───────────────────────────────────────┤
│ HIGH     │ Semgrep JSON Report │ app/views.py:42 │ https://core.example.ru/findings/4811 │
└──────────┴─────────────────────┴─────────────────┴───────────────────────────────────────┘

audit #121: 1 new findings (critical 0, high 1, medium 0, low 0, info 0); fail on: critical,high
https://core.example.ru/products/7  (3m4s)
SECURITY GATE: FAIL - 1 high
  • Таблица по сканерам: "новых" - добавлено этим аудитом, "известных" - совпало с уже известными, "дублей" - повторы внутри отчёта.
  • В режиме open в таблице уровней есть колонка "не разобрано", а список блокирующих называется "Не разобрано, держит мерж: N".
  • Medium, low и info попадают в список, только если входят в ASOC_FAIL_ON. Иначе они видны числом в таблице уровней.
  • Предупреждение warning: N of M scanner reports were imported - одна из джоб-сканеров не дала отчёт. Проверьте её лог в Radar.

Отчёт JUnit

Файл ASOC_JUNIT (по умолчанию security-gate.xml), набор тестов asoc security gate:

Ситуация Тест-кейсы
ошибка (код 2) один упавший кейс security gate с текстом ошибки
новых находок нет один кейс no new findings со сводкой
есть новые находки кейс на каждую новую находку: [HIGH] <название> (<файл:строка>). Упавшие - только блокирующих уровней

В режиме open отчёт JUnit содержит только новые находки этого скана. Старые неразобранные находки, из-за которых упала джоба, видны в логе джобы.

GitLab CI

Минимальная джоба. ASOC_URL и ASOC_TOKEN заданы переменными CI/CD группы. Режим new задан явно: он работает с пином коммита, который включён по умолчанию, см. Рекомендуемые сочетания.

security-check:
  stage: test
  image: registry.example.ru/security-checker:<версия>
  variables:
    ASOC_GATE_MODE: new
  script: [asoc-gate]
  artifacts:
    when: always
    reports:
      junit: security-gate.xml

Рабочий шаблон: проверка merge request и ветки по умолчанию, один запуск на ветку, таймаут с запасом:

security-check:
  stage: test
  image: registry.example.ru/security-checker:<версия>
  variables:
    ASOC_URL: https://core.example.ru
    ASOC_GATE_MODE: new
    ASOC_FAIL_ON: critical,high
    ASOC_TIMEOUT: 40m
  script: [asoc-gate]
  timeout: 50m
  resource_group: security-check-$CI_COMMIT_REF_SLUG
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  artifacts:
    when: always
    reports:
      junit: security-gate.xml
  • resource_group выполняет запуски на одной ветке по очереди. Так каждый запуск находит свой аудит.
  • В пайплайне merge request ветка берётся из CI_MERGE_REQUEST_SOURCE_BRANCH_NAME, иначе из CI_COMMIT_REF_NAME; коммит - из CI_COMMIT_SHA.

Неблокирующий запуск для пилота - вердикт в логе и JUnit, пайплайн не падает на находках:

security-check:
  stage: test
  image: registry.example.ru/security-checker:<версия>
  variables:
    ASOC_FAIL_ON: none
  script: [asoc-gate]
  artifacts:
    when: always
    reports:
      junit: security-gate.xml

Ошибки (код 2) роняют джобу и в этом режиме.

Блокировка слияния

Код выхода сам по себе слияние не запрещает. Включите в GitLab:

  1. В настройках проекта или группы - проверку слияния "Pipelines must succeed" (Settings → Merge requests → Merge checks).
  2. Для main и master - защиту ветки без прямого push (Settings → Repository → Protected branches).
  3. Убедитесь, что джоба Security checker попадает в пайплайн merge request: правило rules выше.

Теперь merge request с упавшей джобой нельзя слить, пока находки не разобраны в Core или не исправлены.

Security checker и Quality gate дополняют друг друга: Security checker держит слияние из CI, quality gate пишет статус коммита и комментарии в pull request.

Параллельные запуски и нагрузка

  • Запуски по разным веткам или разным коммитам одного репозитория не мешают друг другу: свой аудит Security checker находит по ветке <ветка>:<sha> в записи аудита.
  • Запуски на одной ветке выполняйте по очереди: задайте resource_group.
  • Каждый запуск - полный скан репозитория в Radar. Число одновременных сканов ограничено числом процессов исполнителя пайплайнов Radar, остальные ждут в очереди, см. Как работает Radar.

Другие CI-системы и локальный запуск

Шаблоны на этой странице - для GitLab CI. В GitHub Actions Security checker берёт репозиторий, ветку и коммит из GITHUB_REPOSITORY, GITHUB_HEAD_REF или GITHUB_REF_NAME, GITHUB_SHA, в Jenkins с плагином Git - из GIT_URL, GIT_BRANCH без префикса origin/, GIT_COMMIT. Адрес и токен Core передавайте через ASOC_URL и ASOC_TOKEN или ASOC_DSN.

Вне CI Security checker читает .git текущего каталога: URL remote origin, текущую ветку и её коммит.

docker run --rm -v "$PWD":/src -w /src \
  -e ASOC_URL=https://core.example.ru \
  -e ASOC_TOKEN=<токен> \
  registry.example.ru/security-checker:<версия>

Radar клонирует ветку из системы контроля версий, поэтому незапушенные изменения не сканируются. С пином коммита локальный коммит должен быть запушен.

Диагностика

Сообщение Причина Что сделать
asoc-gate: set ASOC_DSN=https://host (or ASOC_URL) не задан адрес Core задайте ASOC_URL или ASOC_DSN
asoc-gate: нет токена портала ... не задан токен задайте ASOC_TOKEN или токен в ASOC_DSN
cannot detect repository: set ASOC_REPO=group/project джоба не в CI и нет .git задайте ASOC_REPO
cannot detect branch: set ASOC_BRANCH не определилась ветка, например detached HEAD вне CI задайте ASOC_BRANCH
ERROR: repository "<путь>" is not registered in ASOC ... нет ассета-репозитория с таким путём. В скобках - похожие ассеты заведите ассет в продукте или поправьте SSH URL ассета
... -> HTTP 401 токен неверный или ротирован обновите токен в переменных CI
... -> HTTP 403 у роли нет нужного права или лицензия без доступа к Radar проверьте роль по таблице выше и тип лицензии
timeout: auditor pipeline for audit #N did not finish in 30m0s Radar не получил задачу или не успел. Ошибка отправки задачи в Radar видна в журнале воркера Core проверьте "Статус подключения" в Core ("Radar" → "Настройки"), очередь на экране "Пайплайны" Radar, увеличьте ASOC_TIMEOUT
ERROR: auditor pipeline #N ended with status KILLED джоба с выключенным "Разрешить падение" упала или пайплайн остановлен вручную откройте лог пайплайна в Radar
ERROR: auditor pipeline #N ended with status TIMED_OUT сработал таймаут пайплайна или джобы Radar увеличьте таймауты в Radar
audit #N produced no results at all ... сканеры не дали ни одной находки, даже известной. Чаще всего Code Downloader не склонировал код проверьте лог Code Downloader: SSH-ключ, доступ к репозиторию, коммит. Для действительно пустого репозитория - ASOC_ALLOW_EMPTY=1
audit #N added X findings but only Y are visible for branch ... новые находки аудита не видны по ветке джобы повторите запуск; не запускайте гейт параллельно на одной ветке
x509: certificate signed by unknown authority сертификат Core выпущен корпоративным центром сертификации передайте цепочку в SSL_CERT_FILE