SARIF統合
任意のコードスキャンツール (CodeQL、Semgrep、Snyk、Checkmarx、Trivy、Bandit、GitLab SAST、OWASP ZAP) のSARIF出力をSecureCodingHubに送信します。実行内の一意の各CWEは、コミット作成者のアカウントの課題となります — 通常マージから数秒以内に。
CIが適切なトリガーである理由
汎用的な「すべての開発者が同じOWASP Top 10コースを受講する」展開は、完了率が低く、定着率はさらに低くなります。CI統合はモデルを反転します: トレーニングは問題を導入した開発者にピンポイントで、必要な脆弱性クラスにピンポイントで、それを露呈したマージに紐づいた14日間の期限とともに割り当てられます。これは監査人がジャストインタイムなセキュアコーディングトレーニングと呼ぶものです。
エンドツーエンドの仕組み
SASTツールがCIジョブの一部としてSARIF 2.1.0ファイルを生成します。
CIステップがコミットメタデータをリクエストヘッダーとしてファイルを/api/public/v1/sarifにPOSTします。
SecureCodingHubは各resultを解析し、そのCWEタグを抽出し、内部CWE-to-topicマップでそれぞれを検索します。
マップされた各一意のトピックについて、X-Commit-Author-Emailと一致するメールアドレスのユーザーに14日間の期限で課題が作成されます。
同期レスポンスが返すのと同じペイロードでsarif.ingested Webhookが発火します。
エンドポイント
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 | — | リポジトリスラッグ (owner/name)。監査のために記録されます。 |
X-Branch | — | ブランチ名。監査のために記録されます。 |
X-Commit-Sha | 推奨 | コミットハッシュ。X-Repoと組み合わせて24時間冪等性に使用されます。 |
X-Commit-Author-Email | 推奨 | コミット作成者のメールアドレス。SecureCodingHubユーザーに解決され、課題がそのユーザーに紐づけられます。メールがユーザーと一致しない場合、実行は取り込まれますが課題は作成されません。 |
レスポンス
{
"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— 致命的でない警告 (例: 無効なコミット作成者メール形式)。
冪等性
同じSARIFファイルの再取り込みは安全です。タプル(organization, repo, commit_sha)が冪等性キーです — 24時間ウィンドウ内の重複は、元のingestionIdとassignmentsCreated: 0を含む200を返します。これは、同じコミット (リベース、手動再実行、PRへのプッシュ) で再実行されるCIワークフローが重複する課題を積み重ねないことを意味します。
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_BRANCHGitLab 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/sarifCWEカバレッジ
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 MBのペイロードを受け入れます。実世界のほとんどの単一リポジトリのSASTレポートは、これを十分下回ります。上限を超えるエンタープライズ規模のモノレポレポートがある場合は、ツールドライバごとに分割し (ドライバごとに1つのSARIF実行)、それぞれをPOSTしてください — 同じコミットSHAの下で冪等になります。
失敗モード
| 症状 | 可能性の高い原因 |
|---|---|
200にassignmentsCreated: 0 | コミット作成者のメールが組織のユーザーではないか、すべてのCWEがすでに割り当て済みです。 |
200に空でないunmappedCwes | 検出結果がまだトレーニングトピックを持たないCWEを参照しています。リストをサポートに転送してください。 |
400 empty_body | curlに--data-binary @file.sarifがありませんでした。 |
413 body_too_large | SARIFが10 MBを超えています。ドライバごとに分割してください。 |
idempotency:で始まるnotesエントリを持つ200 | 同じ(repo, commit)がすでに24時間以内に取り込まれています。レスポンスはそれ以外、assignmentsCreated: 0を含む新規取り込みと同一です — 無視して安全です。 |