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

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

Загрузка отчётов из CI

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

Если сканер уже работает в вашем CI, отправьте его отчёт в Core напрямую: POST /api/v1/scan/import/. Core примет отчёт, ответит 202 и разберёт его асинхронно - так же, как отчёты из Radar. Загрузка из интерфейса описана на странице Импорт отчётов.

Перед началом

  • Сервисный пользователь и токен. Роль с правом "Уязвимости" - "Изменение". Для проверки результата через аудит добавьте "Продукты" - "Просмотр". Токен - "Профиль" → "Токен аутентификации API", см. Пользователи.
  • Имя сканера. scanner_name - точное название сканера в Core: "Глобальные настройки" → "Сканеры". Список встроенных - Справочник сканеров.
  • Адрес. https://core.example.ru/api/v1/scan/import/ - со слешем на конце.
  • Продукт. Передайте имя и тип продукта или один ассет, который уже есть в Core. Правила - ниже.

Храните токен в защищённой маскированной переменной CI, а не в файле пайплайна.

Параметры запроса

Запрос - multipart/form-data, заголовок Authorization: Token <токен>.

Поле Обязательное Описание
scanner_name да название сканера в Core
file file или report файл отчёта. Core читает его как текст в UTF-8
report file или report текст отчёта вместо файла
product_name вместе с product_type продукт. Если его нет, Core создаёт
product_type вместе с product_name тип продукта. Если его нет, Core создаёт
repository нет ассет "Репозиторий" - SSH URL, например git@gitlab.example.ru:team/payments.git
docker_image нет ассет "Docker-образ", без тега
domain нет ассет "Домен"
host нет ассет "Хост"
cloud_account нет ассет "Облачный аккаунт"
branch нет ветка находок
docker_image_tag нет тег образа
custom_field нет своё поле находок, до 300 символов
audit_id нет номер существующего аудита. Не задан - Core создаёт новый аудит, если указан хотя бы один ассет

Переданные ассеты Core создаёт в продукте, если их там нет.

Как определяется продукт

  1. Переданы product_name и product_type - используется этот продукт.
  2. Иначе передан ровно один ассет: продукт ассета, если ассет есть в Core; продукт по умолчанию, если ассета нет.
  3. Ничего не подошло - ответ 400.

Если в "Глобальные настройки" → "Рабочий процесс" включено "Включить уникальные ассеты", Core пропускает product_name и product_type и определяет продукт по ассету.

Ассет - тот же, что у Radar

Передавайте в repository тот же SSH URL, что у ассета в Core. Тогда находки из CI и из Radar попадают в один ассет, а Quality gate и Security checker их видят.

Автозакрытие

Если у сканера в Core включено "Автозакрытие", находки, которых нет в новом отчёте, будут решены или удалены. Отправляйте полный отчёт по ассету, а не по изменённым файлам.

Ответ

Успех - 202:

{
  "product_name": "payments",
  "product_type": "Common",
  "scanner_name": "Gitleaks Scan",
  "branch": "main",
  "docker_image_tag": "",
  "custom_field": "",
  "audit_id": 121,
  "assets": {
    "repository": "git@gitlab.example.ru:team/payments.git",
    "docker_image": "",
    "domain": "",
    "cloud_account": "",
    "host": ""
  },
  "user_name": "ci",
  "user_id": 7
}

202 значит, что отчёт принят в очередь разбора. Находки появляются после разбора.

Коды ответа

Код Когда Пример тела
202 отчёт принят см. выше
400 неизвестное имя сканера {"scanner_name": ["\"Gitleaks\" is not a valid choice."]}
400 нет ни file, ни report {"non_field_errors": ["At least one of the 'file' or 'report' fields is required."]}
400 несуществующий audit_id {"audit_id": ["Audit with id 999 does not exist"]}
400 продукт не определился ["Specify product name and product type or set default product"]
400 нет продукта и передано несколько ассетов {"detail": "Either specify product and product type or a single asset"}
400 ассет найден в нескольких продуктах {"detail": "Only one specified asset should exist"}
401 нет заголовка или токен неверный {"detail": "No credentials provided"}, {"detail": "Invalid token."}
403 у роли нет права "Уязвимости" - "Изменение" {"detail": "You do not have permission to perform this action."}

Проверка результата

Номер аудита из ответа покажет итог разбора:

curl -s "https://core.example.ru/api/v1/audit/121/" \
  -H "Authorization: Token $SF_TOKEN"

Когда разбор закончится, в scanners появится запись сканера: added_findings_count - новые находки, deduplicated_findings_count - совпавшие с известными, report_duplicates_count - повторы внутри отчёта. В интерфейсе - "Аудиты" → аудит.

GitLab CI

Пример: Gitleaks в одной джобе, отправка в Core - в следующей. SF_TOKEN - маскированная переменная CI/CD группы.

variables:
  SF_URL: https://core.example.ru
  SF_PRODUCT_NAME: payments
  SF_PRODUCT_TYPE: Common
  SF_REPOSITORY: git@gitlab.example.ru:$CI_PROJECT_PATH.git

stages: [test, report]

gitleaks:
  stage: test
  image: registry.example.ru/gitleaks:<версия>
  script:
    - gitleaks detect -s . -r gitleaks.json --exit-code 0
  artifacts:
    when: always
    paths: [gitleaks.json]

.sf-import:
  stage: report
  image: registry.example.ru/curl:<версия>   # любой образ с curl
  script:
    - |
      curl --silent --show-error --fail-with-body \
        -H "Authorization: Token $SF_TOKEN" \
        -F "scanner_name=$SF_SCANNER" \
        -F "product_name=$SF_PRODUCT_NAME" \
        -F "product_type=$SF_PRODUCT_TYPE" \
        -F "repository=$SF_REPOSITORY" \
        -F "branch=$CI_COMMIT_REF_NAME" \
        -F "file=@$SF_REPORT" \
        "$SF_URL/api/v1/scan/import/"

gitleaks-import:
  extends: .sf-import
  needs: [gitleaks]
  variables:
    SF_SCANNER: Gitleaks Scan
    SF_REPORT: gitleaks.json
  • Шаблон .sf-import переиспользуйте для других сканеров: меняйте SF_SCANNER и SF_REPORT.
  • --fail-with-body роняет джобу при ответе не 2xx и печатает тело ошибки. Нужен curl 7.76 или новее.
  • Не передавайте в repository переменную CI_REPOSITORY_URL: она содержит токен джобы.
  • Сканер, который возвращает ненулевой код при находках, запускайте так, чтобы отчёт всё равно сохранился: --exit-code 0 у gitleaks или allow_failure: true у джобы.

Jenkins

Отправка - тот же запрос curl. Токен храните в Jenkins Credentials как Secret text.

pipeline {
  agent any
  environment {
    SF_URL = 'https://core.example.ru'
  }
  stages {
    stage('Scan') {
      steps {
        sh 'trivy fs --format json --output trivy.json .'
      }
    }
    stage('Import to SecurityFlow') {
      steps {
        withCredentials([string(credentialsId: 'sf-core-token', variable: 'SF_TOKEN')]) {
          sh '''
            curl --silent --show-error --fail-with-body \
              -H "Authorization: Token $SF_TOKEN" \
              -F "scanner_name=Trivy Scan" \
              -F "product_name=payments" \
              -F "product_type=Common" \
              -F "repository=git@gitlab.example.ru:team/payments.git" \
              -F "branch=${GIT_BRANCH#origin/}" \
              -F "file=@trivy.json" \
              "$SF_URL/api/v1/scan/import/"
          '''
        }
      }
    }
  }
}
  • Скрипт sh в одинарных кавычках: токен подставляет оболочка, а не Groovy, и он не попадает в журнал сборки.
  • GIT_BRANCH плагина Git содержит префикс origin/ - конструкция ${GIT_BRANCH#origin/} его убирает.

Типовые ошибки

Симптом Причина Что сделать
400 is not a valid choice scanner_name не совпадает с названием сканера скопируйте название из "Глобальные настройки" → "Сканеры"
401 No credentials provided нет заголовка или схема не Token заголовок строго Authorization: Token <токен>
202, но находок нет импортёр не разобрал отчёт: другой формат или пустой файл проверьте формат отчёта для этого сканера, загрузите тот же файл через интерфейс
ответ 404 адрес без слеша на конце используйте /api/v1/scan/import/
500 файл не в UTF-8 сохраните отчёт в UTF-8