Интеграция SARIF
Отправляйте SARIF-вывод любого инструмента сканирования кода (CodeQL, Semgrep, Snyk, Checkmarx, Trivy, Bandit, GitLab SAST, OWASP ZAP) в SecureCodingHub. Каждый уникальный CWE в ране становится заданием на счету автора коммита — обычно в течение секунд после мержа.
Почему CI — правильный триггер
Универсальные раскатки «каждый разработчик проходит один и тот же курс OWASP Top 10» имеют низкое завершение и ещё более низкое удержание. CI-интеграция переворачивает модель: обучение назначается конкретно разработчику, который ввёл проблему, конкретно на классе уязвимости, который ему нужен, со сроком 14 дней, привязанным к мержу, который её выявил. Это то, что аудиторы называют своевременное обучение безопасному кодированию.
Как это работает от начала до конца
Ваш SAST-инструмент производит файл SARIF 2.1.0 как часть CI-задачи.
CI-шаг делает POST файла на /api/public/v1/sarif с метаданными коммита как заголовками запроса.
SecureCodingHub парсит каждый result, извлекает его CWE-теги и ищет каждый в нашей внутренней карте CWE-к-теме.
Для каждой уникальной сопоставленной темы создаётся задание на пользователя, чей email совпадает с X-Commit-Author-Email, со сроком 14 дней.
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-Repo | — | Slug репозитория (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-89CWE-89cwe:89
Теги читаются и из result.properties.tags, и из соответствующего tool.driver.rules[].properties.tags.
Лимит размера тела
Конечная точка SARIF принимает полезные нагрузки до 10 МБ без сжатия. Большинство реальных SAST-отчётов для одного репозитория значительно ниже этого лимита. Если у Вас отчёт корпоративного масштаба для монорепозитория, превышающий лимит, разделите по драйверу инструмента (один SARIF-ран на драйвер) и сделайте POST каждой части — они будут идемпотентны под одним и тем же commit SHA.
Режимы сбоя
| Симптом | Вероятная причина |
|---|---|
200 с assignmentsCreated: 0 | Email автора коммита не является пользователем в Вашей организации, или все CWE уже были назначены. |
200 с непустым unmappedCwes | Находки ссылаются на CWE, для которых ещё нет учебной темы. Перешлите список в поддержку. |
400 empty_body | Curl был без --data-binary @file.sarif. |
413 body_too_large | SARIF превышает 10 МБ. Разделите по драйверу. |
200 с записью notes, начинающейся с idempotency: | Тот же (repo, commit) уже был принят в течение 24 ч. Ответ в остальном идентичен свежему приёму с assignmentsCreated: 0 — безопасно игнорировать. |