Документация/API и Webhook'и/Интеграция SARIF

Интеграция SARIF

Отправляйте SARIF-вывод любого инструмента сканирования кода (CodeQL, Semgrep, Snyk, Checkmarx, Trivy, Bandit, GitLab SAST, OWASP ZAP) в SecureCodingHub. Каждый уникальный CWE в ране становится заданием на счету автора коммита — обычно в течение секунд после мержа.

Почему CI — правильный триггер

Универсальные раскатки «каждый разработчик проходит один и тот же курс OWASP Top 10» имеют низкое завершение и ещё более низкое удержание. CI-интеграция переворачивает модель: обучение назначается конкретно разработчику, который ввёл проблему, конкретно на классе уязвимости, который ему нужен, со сроком 14 дней, привязанным к мержу, который её выявил. Это то, что аудиторы называют своевременное обучение безопасному кодированию.

Как это работает от начала до конца

1

Ваш SAST-инструмент производит файл SARIF 2.1.0 как часть CI-задачи.

2

CI-шаг делает POST файла на /api/public/v1/sarif с метаданными коммита как заголовками запроса.

3

SecureCodingHub парсит каждый result, извлекает его CWE-теги и ищет каждый в нашей внутренней карте CWE-к-теме.

4

Для каждой уникальной сопоставленной темы создаётся задание на пользователя, чей email совпадает с X-Commit-Author-Email, со сроком 14 дней.

5

Webhook sarif.ingested срабатывает с той же полезной нагрузкой, что возвращает синхронный ответ.

Конечная точка

POST /api/public/v1/sarif
Authorization: Bearer scs_live_…
Content-Type: application/json
X-Source: github-codeql
X-Repo: acme/payments-api
X-Branch: main
X-Commit-Sha: 9c2f4b8e0c1d…
X-Commit-Author-Email: jane.smith@acme.com

<SARIF 2.1.0 JSON body, up to 10 MB>

Требуемая область действия: sarif:ingest.

Заголовки запроса

ЗаголовокОбязательноЗначение
X-SourceСвободный идентификатор инструмента — например, github-codeql, snyk, semgrep-ci. Записывается для аудита.
X-RepoSlug репозитория (owner/name). Записывается для аудита.
X-BranchИмя ветки. Записывается для аудита.
X-Commit-ShaрекомендуетсяХеш коммита. Используется вместе с X-Repo для 24-часовой идемпотентности.
X-Commit-Author-EmailрекомендуетсяEmail автора коммита. Разрешается в пользователя SecureCodingHub; задания привязываются к нему. Если email не совпадает с пользователем, ран всё равно принимается, но задания не создаются.

Ответ

{
  "ingestionId": "7e4c9d09-86ca-4125-bbd8-7fae54c8f654",
  "findingsCount": 2,
  "uniqueCwes": ["CWE-79", "CWE-89"],
  "unmappedCwes": [],
  "assignmentsCreated": 12,
  "notes": []
}
  • findingsCount — общее число распарсенных записей result.
  • uniqueCwes — набор CWE, упомянутых по всем находкам.
  • unmappedCwes — CWE, которые мы не смогли сопоставить с темой. Отправьте в поддержку, если хотите, чтобы покрытие было добавлено.
  • assignmentsCreated — количество новых записей заданий (существующие дубликаты пропускаются).
  • notes — нефатальные предупреждения (например, неверный формат email автора коммита).

Идемпотентность

Повторный приём того же файла SARIF безопасен. Кортеж (organization, repo, commit_sha) — это ключ идемпотентности — дубликат внутри 24-часового окна возвращает 200 с исходным ingestionId и assignmentsCreated: 0. Это означает, что CI-воркфлоу, которые перезапускаются на том же коммите (rebase, ручной перезапуск, push в PR), не накапливают дублирующиеся задания.

Пример GitHub Actions

Вставьте этот шаг в воркфлоу после того, как Ваша задача CodeQL или Semgrep произвела results.sarif:

- name: Send SARIF to SecureCodingHub
  if: always()
  run: |
    curl -sS -f -X POST \
      -H "Authorization: Bearer $SCH_API_KEY" \
      -H "Content-Type: application/json" \
      -H "X-Source: github-codeql" \
      -H "X-Repo: ${{ github.repository }}" \
      -H "X-Branch: ${{ github.ref_name }}" \
      -H "X-Commit-Sha: ${{ github.sha }}" \
      -H "X-Commit-Author-Email: ${{ github.event.head_commit.author.email }}" \
      --data-binary @results.sarif \
      https://api.limeplate.com/api/public/v1/sarif
  env:
    SCH_API_KEY: ${{ secrets.SCH_API_KEY }}

Храните ключ в секретах репозитория или организации. Защита if: always() гарантирует, что находки отправляются, даже если задача безопасности пометила сборку как неудачную — а это именно тот момент, когда Вы больше всего хотите, чтобы учебные задания сработали.

Пример GitLab CI

send_sarif_to_sch:
  stage: post-security
  image: curlimages/curl:latest
  needs: ["sast"]
  script:
    - >
      curl -sS -f -X POST
      -H "Authorization: Bearer $SCH_API_KEY"
      -H "Content-Type: application/json"
      -H "X-Source: gitlab-sast"
      -H "X-Repo: $CI_PROJECT_PATH"
      -H "X-Branch: $CI_COMMIT_BRANCH"
      -H "X-Commit-Sha: $CI_COMMIT_SHA"
      -H "X-Commit-Author-Email: $GITLAB_USER_EMAIL"
      --data-binary @gl-sast-report.json
      https://api.limeplate.com/api/public/v1/sarif
  rules:
    - if: $CI_COMMIT_BRANCH

Отчёт GitLab SAST совместим с SARIF — конверсия не нужна.

Пример Semgrep

semgrep ci --sarif --output results.sarif
curl -sS -f -X POST \
  -H "Authorization: Bearer $SCH_API_KEY" \
  -H "X-Source: semgrep" \
  -H "X-Repo: $REPO" \
  -H "X-Commit-Sha: $COMMIT" \
  -H "X-Commit-Author-Email: $AUTHOR_EMAIL" \
  --data-binary @results.sarif \
  https://api.limeplate.com/api/public/v1/sarif

Покрытие CWE

SecureCodingHub в настоящее время сопоставляет OWASP Top 10, OWASP API Security Top 10, OWASP Mobile Top 10 и CWE Top 25 с учебными темами. Находки, помеченные CWE ID вне этого набора, появляются в списке unmappedCwes ответа. Распространённые форматы, которые мы принимаем со стороны SARIF-тега:

  • external/cwe/cwe-89 (стандарт CodeQL)
  • cwe-89
  • CWE-89
  • cwe:89

Теги читаются и из result.properties.tags, и из соответствующего tool.driver.rules[].properties.tags.

Лимит размера тела

Конечная точка SARIF принимает полезные нагрузки до 10 МБ без сжатия. Большинство реальных SAST-отчётов для одного репозитория значительно ниже этого лимита. Если у Вас отчёт корпоративного масштаба для монорепозитория, превышающий лимит, разделите по драйверу инструмента (один SARIF-ран на драйвер) и сделайте POST каждой части — они будут идемпотентны под одним и тем же commit SHA.

Режимы сбоя

СимптомВероятная причина
200 с assignmentsCreated: 0Email автора коммита не является пользователем в Вашей организации, или все CWE уже были назначены.
200 с непустым unmappedCwesНаходки ссылаются на CWE, для которых ещё нет учебной темы. Перешлите список в поддержку.
400 empty_bodyCurl был без --data-binary @file.sarif.
413 body_too_largeSARIF превышает 10 МБ. Разделите по драйверу.
200 с записью notes, начинающейся с idempotency:Тот же (repo, commit) уже был принят в течение 24 ч. Ответ в остальном идентичен свежему приёму с assignmentsCreated: 0 — безопасно игнорировать.