# セキュリティをCIで止める — GitHub Actions・SARIF・『高確度のみブロック』の設計

> セキュリティ検査をCIに組み込み、高確度の検出だけでビルドを止める設計。GitHub ActionsでのSAST実行、SARIFによるコードスキャニング連携、誤検知でCIを壊さない（静的×動的の相関で確証のみブロック）運用を、実際のYAMLとコマンドで解説します。

- 公開日: 2026-06-28
- 著者: 友田 陽大
- タグ: Next.js, セキュリティ, アーキテクチャ設計, TypeScript
- URL: https://tomodahinata.com/blog/nextjs-supabase-security-ci-sarif-github-actions-guide
- カテゴリ: アプリ層セキュリティ
- 総合ガイド: https://tomodahinata.com/blog/nextjs-supabase-application-security-guide

## 要点

- セキュリティ検査はPRの時点でCIに入れて初めて意味を持つ——本番に出た後ではなく、マージ前に脆弱性を止める『シフトレフト』が前提。検査が遅いほど直すコストは跳ね上がる
- SARIFは静的解析結果の標準フォーマット。ツール固有の出力をSARIFに統一すれば、GitHubのコードスキャニングのタブに表示され、PRの差分に行単位の注釈が付く
- GitHub ActionsでSASTを走らせ、結果をSARIFで `upload-sarif` に渡すだけ。鍵は `permissions: security-events: write` と `contents: read` の最小権限と、鍵レス（OIDC）運用
- CIゲートの最大の敵は誤検知。ノイズだらけのゲートは必ず無視・無効化される。だから『高確度だけブロック』——静的の「疑い」を動的プローブで再現できたものに格上げ（SAST×DAST相関）し、確証のみを赤にする
- 正直に言うと、CIゲートはセキュリティの「仕組み化」であって、設計レビュー・手動監査の代わりにはならない。ツールは認可の「正しさ」を証明しない。継続運用の歯車として補完するもの

---

最初に結論を述べます。**セキュリティ検査は「人が思い出したときに手元で走らせるもの」である限り、いつか必ず忘れられます。** 価値を生むのは、PRを開いた瞬間にCIが自動で走り、**高確度の脆弱性だけがマージをブロックする**仕組みにしたときだけです。本記事は、その仕組みを GitHub Actions と SARIF で組む方法を、実際のYAMLとコマンドで示します。

ただし、もう一つ最初に正直に言っておきます。**CIゲートはセキュリティの"仕組み化"であって、設計レビューや手動監査の代わりにはなりません。** ツールは「よくある罠を踏んでいないか」を機械的に番をするだけで、あなたの認可ロジックが「正しいか」は証明しません。本記事が扱うのは、その境界を踏まえたうえで——**自動化できる検査を、誤検知でCIを壊さずに、継続運用の歯車として組み込む**設計です。

---

## 1. なぜ「PRで止める」のか——シフトレフトの経済学

脆弱性は、見つかるのが遅いほど直すコストが跳ね上がります。

| 見つかるタイミング | 状態 | 直すコスト |
|---|---|---|
| **コーディング中（手元）** | まだコミットしていない | ほぼゼロ。書き直すだけ |
| **PRレビュー（CI）** | マージ前。差分が小さい | 低い。文脈が新鮮なうちに直せる |
| **本番リリース後** | デプロイ済み・データに影響 | 高い。調査・パッチ・場合により事故対応 |
| **インシデントとして発覚** | 漏洩・悪用が進行 | 最悪。信頼の損失まで含む |

「シフトレフト（shift left）」とは、この表の上の方——できるだけ**左（＝開発の早い段階）**で問題を潰す思想です。CIにセキュリティ検査を入れる本質は、ここにあります。**本番に出た脆弱性を後から探すのではなく、PRの時点で「そもそもマージさせない」。**

重要なのは、これが**人間の規律に依存しない**点です。「コミット前にスキャンを忘れずに」という運用は、忙しい日に必ず破られます。一方、CIゲートは**忘れようがない**——PRを開けば自動で走り、赤ければマージボタンが押せない。セキュリティを「気をつける」から「構造で強制する」へ移すのが、CIゲートの第一の価値です。

この発想は、セキュリティに限らずAI生成コード全般の品質統制と地続きです。型・テスト・セキュリティをCIで束ねる考え方は[AI駆動開発の品質ゲート設計](/blog/ai-driven-development-quality-gates-ci-type-safety-test-security)に整理しました。本記事はその「セキュリティ検査の歯車」を、SARIFとGitHub Actionsで具体化したものです。

> なお、何を自動化で潰し、何を設計で守るのか——という全体地図は[Next.js × Supabase アプリケーションセキュリティ完全ガイド](/blog/nextjs-supabase-application-security-guide)にまとめています。本記事はそこで言う「水平統制・注入クラスの検出をCIに常設する」部分の実装編です。

---

## 2. SARIFとは何か——「検査結果」の共通言語

CIでセキュリティ検査を回すとき、最初にぶつかる問題があります。**ツールごとに出力フォーマットがバラバラ**だということです。あるツールはJSON、別のツールはテキスト、また別のはJUnit XML——これではGitHubのUIに統一して表示できず、ツールを乗り換えるたびに連携を書き直すことになります。

ここで効くのが **SARIF（Static Analysis Results Interchange Format）** です。SARIFは、OASISが標準化した**静的解析結果を表現するためのJSONベースの標準フォーマット**です（[OASIS SARIF v2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html)）。どんなSASTツールでも、結果をSARIFで吐けば、それを受け取る側（GitHub等）は中身を統一的に扱えます。

SARIFの最小構造は、ざっくりこうです。

```ts
// SARIF v2.1.0 の骨格（型のイメージ）。実際のファイルはツールが生成する
type Sarif = {
  version: "2.1.0";
  runs: Array<{
    tool: { driver: { name: string; rules: Array<{ id: string }> } };
    results: Array<{
      ruleId: string;                 // どの検査ルールに当たったか
      level: "error" | "warning" | "note"; // 深刻度 → CIの赤/黄を決める
      message: { text: string };      // 人間が読む説明
      locations: Array<{              // どのファイルの何行目か
        physicalLocation: {
          artifactLocation: { uri: string };
          region: { startLine: number };
        };
      }>;
    }>;
  }>;
};
```

ポイントは3つです。

1. **`ruleId`** — どの検査に引っかかったか。GitHub上でルール単位の集計・抑制ができます。
2. **`level`** — `error` / `warning` / `note`。これが**CIで何を赤にし、何を黄色（警告）に留めるか**の根拠になります。後述の「高確度のみブロック」は、ここをどう割り当てるかの設計です。
3. **`locations`** — ファイルと行番号。これがあるから、GitHubは**PRの差分に行単位の注釈**を打てます。

つまりSARIFは「検査結果の共通言語」です。ツールを乗り換えてもCI連携はそのまま、UIへの表示もそのまま。検査ロジックと表示・運用を**疎結合**にできるのが、標準フォーマットを使う最大の利点です。

---

## 3. GitHub コードスキャニングに載せる——差分注釈とSecurityタブ

SARIFを吐けたら、次はそれをGitHubに食わせます。GitHubには **コードスキャニング（code scanning）** という機能があり、SARIFをアップロードすると2つの場所に結果が現れます（[GitHub code scanning docs](https://docs.github.com/en/code-security/code-scanning)）。

- **Security タブ** — リポジトリの全アラートが一覧化され、ルール別・深刻度別に管理できる。既知の指摘を「無視（dismiss）」したり、再発を追跡したりできる。
- **PRの差分（Files changed）** — 検出された行に**インラインの注釈**が付く。レビュアーは「この変更がこの脆弱性を入れた」を、コードを読みながらその場で見られる。

この「PRの差分に注釈が出る」体験が決定的です。セキュリティの指摘が、別のダッシュボードや週次レポートではなく、**まさにレビューしているコードの横**に出る。直す文脈が新鮮なうちに、最小の差分で直せます。第1節のシフトレフトを、UIのレベルで実現するのがコードスキャニングです。

GitHubが受け取るのはあくまで**SARIF**なので、ここでも特定ツールへのロックインはありません。SARIFさえ吐ければ、自作ツールでもOSSでも、同じレールに載ります。

---

## 4. 実YAML——GitHub ActionsでSASTを走らせ、SARIFを上げる

ここからが実装の中心です。私が公開しているOSSセキュリティスキャナ **Aegis** には GitHub Action が同梱されており、`uses` 一行でCIに組み込めます。Aegisは Next.js × Supabase の注入クラス（汚染入力→危険シンク）やRLS/認可の弱点を検出し、結果を**SARIFで出力**します。

以下を `.github/workflows/security.yml` に置くだけで、push と PR のたびにスキャンが走り、結果がコードスキャニングに載ります。

```yaml
# .github/workflows/security.yml
name: Security
on: [push, pull_request]

jobs:
  aegis:
    runs-on: ubuntu-latest
    permissions:
      contents: read          # コードを読むためだけの最小権限
      security-events: write  # SARIF を Security タブへアップロードするのに必須
    steps:
      - uses: actions/checkout@v4

      - uses: tomodahinata/aegis@main
        with:
          severity: HIGH       # HIGH 以上だけを「赤（ブロック）」に昇格させる

      - uses: github/codeql-action/upload-sarif@v3
        if: always()           # スキャンが失敗しても結果は必ずアップロードする
        with:
          sarif_file: aegis.sarif
```

短いですが、設計上の判断が4つ詰まっています。順に解きます。

### 4-1. `permissions` は最小権限で固定する

ワークフローに与える権限は、**必要なものだけ**に絞ります。

- `contents: read` — リポジトリのコードを読むため。書き込みは不要なので `read`。
- `security-events: write` — SARIFをアップロードしてコードスキャニングのアラートを作るのに**必須**の権限です。これが無いと `upload-sarif` が失敗します。

ここで `write` を広げすぎない（例えば `contents: write` を付けない）のは、**CIワークフロー自体が攻撃面**だからです。サプライチェーン攻撃では、過剰な権限を持つワークフローが踏み台にされます。最小権限の原則（OWASPの[Application Security Verification Standard](https://owasp.org/www-project-application-security-verification-standard/)が一貫して説く思想）は、アプリのコードだけでなく**CIの設定そのもの**にも適用します。

### 4-2. `if: always()` で「結果は必ず上げる」

`upload-sarif` のステップに `if: always()` を付けています。これは、**前のスキャンステップが「脆弱性あり」で失敗（exit 1）しても、SARIFのアップロードは実行する**ためです。

これが無いと、こういう最悪の挙動になります——「HIGHの脆弱性が見つかってスキャンが赤くなった」→「ジョブが止まってアップロードがスキップされる」→「**一番見たい指摘がコードスキャニングに載らない**」。ブロックすることと、指摘を可視化することは別の関心事です。`always()` で後者を必ず保証します。

### 4-3. `severity: HIGH` が「ブロックの閾値」

`with: severity: HIGH` が、本記事の核心である**「高確度のみブロック」の入口**です。これは「HIGH以上の確度・深刻度を持つ検出だけを、CIを赤にする（マージをブロックする）対象にする」という指定です。MEDIUM以下は——後述しますが——SARIFには載せてコードスキャニングで見えるようにしつつ、**ビルドは止めない**（警告に留める）運用が基本になります。

なぜこの閾値設計が決定的なのかは、次節で詳述します。

### 4-4. Action 参照は `tomodahinata/aegis@main`

`uses: tomodahinata/aegis@main` は Aegis Action の正しい参照です。**`v1` のようなタグはまだありません**ので、`@main` を使ってください。本番運用でバージョンを固定したい場合は、`@main` の代わりに**コミットSHAでピン留め**する（`tomodahinata/aegis@<commit-sha>`）のが、サプライチェーン的には最も安全です（タグやブランチは動きうるが、SHAは不変）。

---

## 5. ノイズ問題——「誤検知でCIを壊す」と、ゲートは無視される

ここが、CIセキュリティで**最も多くのチームが失敗する**ポイントです。技術的な難しさではなく、**運用の力学**の問題です。

セキュリティゲートを入れた直後、ツールが大量の指摘を吐いたとします。その多くが誤検知（false positive）——実際には悪用できない、文脈的に問題ない指摘だったとします。すると何が起きるか。

1. PRが**毎回赤くなる**。しかも理由の大半が「直す必要のない指摘」。
2. 開発者は「またノイズか」と**赤を信用しなくなる**。
3. やがて誰かが「とりあえず通すために」**ゲートを `continue-on-error` にするか、ジョブごと無効化**する。
4. **セキュリティゲートが死ぬ。** 本物の脆弱性も一緒にすり抜けるようになる。

これは仮想の話ではなく、CIセキュリティ導入の典型的な失敗曲線です。**「厳しすぎるゲートは、必ず迂回される」**。狼少年と同じで、嘘の警報が続けば、本物の警報も無視されます。

ここから導かれる原則は一つです。

> **CIをブロックする検出は、誤検知率が極めて低いものに限定する。** 残りは「警告として見せる」に留め、ビルドは止めない。ゲートの信頼は「赤が出たら本当にヤバい」という体験でしか育たない。

問題は、「どうやって誤検知率の低い検出だけを選り分けるか」です。深刻度（severity）が高い＝確度が高い、ではありません。**「これはSQLインジェクションかもしれない」という静的解析の"疑い"は、深刻だが不確実**です。この不確実性をどう潰すか——それが次節のSAST×DAST相関です。

---

## 6. 高確度のみブロック——静的の"疑い"を動的で"確証"に格上げする

「高確度だけをブロックする」を実現する鍵は、**確度の出どころを二重化する**ことです。一つの静的解析だけで「これは確実」と断ずるのは難しい。だから、**静的解析（SAST）の疑いを、動的プローブ（DAST）で実際に再現できたか**で格上げします。

### 6-1. 二段階の確度モデル

```text
SAST（静的解析）        ：コードのデータフローを追い、「汚染入力→危険シンク」を疑う
   ↓ 疑い（potential）
DAST（動的プローブ）     ：自分のアプリを実際に叩き、その疑いが再現するか確かめる
   ↓ 再現できた
confirmed（確証）       ：静的×動的が一致 → これだけを CI でブロックする
```

- **SASTは「疑う」**。第2のガイドで詳述した「`params.id` が所有権チェックなしでDBクエリに届く」ようなパターンを、関数内のtaint解析で機械的に拾います。これは速くて網羅的ですが、**文脈次第で誤検知も出る**（例：別の場所で実は絞り込まれている）。
- **DASTは「再現する」**。自分が所有するアプリに対して、2つのアイデンティティ（A・B）を用意し、Aのセッションのまま Bのリソースを叩く——といった**安全・非破壊なプローブ**を実行します。200が返って他人のデータが見えたら、その疑いは**実行時に確証**されます。

そして、**SASTの疑いとDASTの再現が一致したものだけ**を `confirmed-exploitable`（確証済み）として、SARIFの `level: "error"` ＝ CIブロック対象に昇格させます。再現できなかった静的の疑いは `warning`／`note` に留め、コードスキャニングには出すが**ビルドは止めない**。

これが「高確度のみブロック」の実体です。深刻度の高さではなく、**"動的に再現できた"という事実**を、ブロックの根拠にする。

### 6-2. ローカルでの相関——CIに入れる前に手で確かめる

CIに組み込む前に、まず手元でこの相関を体感しておくとよいです。Aegisはスキャンとプローブを別コマンドで提供しています。

```bash
# 1. 静的解析：汚染入力→危険シンク／所有権なしクエリを「疑う」
npx @aegiskit/cli scan

# 2. 動的プローブ：自分のアプリを実際に叩き、疑いが「再現するか」確かめる
#    --correlate で静的の結果と突き合わせ、再現したものを confirmed に格上げ
npx @aegiskit/cli probe http://localhost:3000 --correlate
```

`--correlate` を付けると、`scan` の疑いのうち `probe` で再現したものが**確証済み**としてマークされます。この「確証済みだけが赤」という体験を手元で作っておくと、CIに `severity: HIGH` で入れたときの赤の意味が腑に落ちます。

> **プローブの倫理的境界。** DASTは**自分が所有・管理するアプリ**に対してのみ実行してください。他人のサービスへの無許可のプローブは攻撃と区別がつかず、法的にも問題になります。Aegisのprobeは非破壊（読み取り中心、データを壊さない）設計ですが、対象は必ず自分のステージング/ローカルに限ります。

---

## 7. 段階導入——いきなりブロックしない（warn → block）

「高確度のみブロック」を設計しても、**初日からブロックを有効にするのは悪手**です。既存のコードベースには、まず確実に**過去分の指摘（ベースライン）**が溜まっています。それを全部ブロックにすると、関係ない人のPRまで赤くなり、第5節の「ゲートが死ぬ」曲線に乗ります。

正しい順序は、**warn から始めて、信頼が育ってから block に上げる**です。

### 段階1：警告のみ（ビルドは止めない）

まずは「結果を可視化するが、ブロックはしない」状態から始めます。スキャンステップに `continue-on-error: true` を付け、SARIFのアップロードだけ確実に行います。

```yaml
# 段階1：warn のみ。指摘は Security タブと差分注釈に出すが、CI は赤にしない
- uses: tomodahinata/aegis@main
  with:
    severity: HIGH
  continue-on-error: true   # ← 検出があってもジョブを失敗させない（観察期間）

- uses: github/codeql-action/upload-sarif@v3
  if: always()
  with:
    sarif_file: aegis.sarif
```

この期間に、チームは「どんな指摘が出るか」「どれが本物でどれがノイズか」を学びます。既存の確証済み指摘は、この間にPRで潰すか、後述の抑制で正式に「無視」を記録します。

### 段階2：高確度だけブロックに昇格

ベースラインが片付き、ノイズの傾向が掴めたら、`continue-on-error` を外して**ブロックを有効化**します。

```yaml
# 段階2：confirmed（HIGH かつ動的再現済み）だけがビルドを止める
- uses: tomodahinata/aegis@main
  with:
    severity: HIGH
  # continue-on-error を外す → HIGH 検出でジョブが失敗＝マージ不可になる

- uses: github/codeql-action/upload-sarif@v3
  if: always()
  with:
    sarif_file: aegis.sarif
```

仕上げに、GitHubの**ブランチ保護ルール**で `Security` チェックを「マージ必須」に指定します。これで「赤いPRは構造的にマージできない」状態が完成します。**警告から始め、確証だけをブロックに昇格させる**——この順序が、迂回されないゲートを作る唯一の現実的な道です。

---

## 8. 鍵レス運用（OIDC）——CIに長期シークレットを置かない

CIセキュリティを語るうえで、CI自身のシークレット管理に触れないわけにはいきません。**ゲートを守るワークフローが、鍵の漏洩経路になっては本末転倒**です。

ここまでのAegis Actionの例は、外部サービスへの認証を必要としません（コードを読んでSARIFを出すだけ）。`security-events: write` も `GITHUB_TOKEN` の自動権限で賄われ、**長期シークレットは不要**です。これ自体が良い設計です。

一方で、CIから外部（クラウドやコンテナレジストリ等）にアクセスする必要が出たときは、**長期のアクセスキーをリポジトリのSecretsに置かない**のが鉄則です。代わりに **OIDC（OpenID Connect）** を使い、ジョブ実行時にだけ有効な**短命のトークン**を発行して認証します。

```yaml
# OIDC で短命トークンを取得する例（クラウド連携が必要な場合）
permissions:
  contents: read
  id-token: write   # ← OIDC トークンの発行に必要。長期キーは Secrets に置かない
```

`id-token: write` は、ワークフローが**自身のIDを証明する短命トークン**を取得する権限です。これをクラウド側の信頼設定（特定リポジトリ・特定ブランチからのみ受け付ける）と組み合わせると、**漏れて困る長期鍵がそもそも存在しない**状態になります。これも最小権限・最小有効期間の徹底です。

> 環境変数とシークレットの境界設計そのもの——どこまでをコードに出し、何を秘匿し、いつ起動時検証するか——は、本記事の主題から外れるため別途まとめています（[Next.js × Supabase アプリケーションセキュリティ完全ガイド](/blog/nextjs-supabase-application-security-guide)の「型付きenv境界」を参照）。

---

## 9. RLS/認可の回帰もCIで——SQL検証を相関に足す

注入クラスの検出（SAST）と動的プローブ（DAST）に加えて、**SupabaseのRLS/認可の設計そのもの**もCIで検証できます。これは「相関」の第3の軸です。

Aegisのスキャンは `supabase/migrations/**.sql` を読み、RLS無効のテーブル、`using (true)` の無条件許可、`WITH CHECK` 欠落、`search_path` 未固定の `SECURITY DEFINER` 関数、anonロールへの過剰GRANTといった**構造的な弱点**を検出し、これもSARIFに含めます。RLSの設定ミスの体系的な検出観点は[RLS設定ミスの検出と監査](/blog/supabase-rls-misconfiguration-detection-audit-guide)に詳しくまとめています。

さらに退行を防ぐなら、**pgTAPでRLSの回帰テスト**を書き、CIで「他人の行が見えないこと」を継続的に証明します。これはSARIFとは別系統（テストの赤/緑）ですが、同じCIパイプラインで束ねる価値があります。

```sql
-- pgTAP：別ユーザーのJWTで他人の行が見えないことを回帰テストにする
begin;
select plan(1);
set local role authenticated;
set local request.jwt.claims to '{"sub":"user-b-uuid"}';
select is_empty(
  $$ select * from invoices where user_id = 'user-a-uuid' $$,
  'user B cannot read user A invoices'
);
select * from finish();
rollback;
```

こうして**SAST（コード）・SQL検証（RLS設計）・DAST（実行時再現）・pgTAP（回帰）**の4つを同じCIで回し、**確証が取れたものだけをブロック**に昇格させる——これがCIセキュリティの完成形です。注入や認可がOWASPの[OWASP Top 10](https://owasp.org/www-project-top-ten/)で繰り返し上位に並ぶのは、これらが最も普通に混入するからこそ。だからこそ「人が気をつける」ではなく「CIが番をする」価値があります。

---

## 10. 正直なスコープ——CIゲートは監査の"代わり"ではない

ここは本記事で最も強調したい点です。**CIゲートは、設計レビューや手動監査を置き換えません。**

CIに常設できるのは、本質的に**「機械的に判定できるパターン」**です——汚染入力が危険シンクに届いているか、RLSが無効になっていないか、所有権チェックが構文的に抜けていないか。これらは価値ある検査ですが、見ているのはあくまで**コードとSQLの"形"**です。

一方、**「この認可ロジックは事業ルールとして正しいか」「このテナント帰属の根拠（`app_metadata` か `user_metadata` か）は妥当か」「この状態遷移はありえない順序を許していないか」**——こうした判断は、**あなたのデータモデルと事業の意味**を理解した人間にしか下せません。CIゲートがどれだけ緑でも、それは「よくある罠は踏んでいない」を意味するだけで、「認可が正しい」を意味しません。

> **いかなるツールも、認可の"正しさ"は証明しない。** データフロー解析は関数内（intraprocedural）が基本で、モジュールやフレームワークを跨ぐ流れは見逃します。動的プローブも「試した経路で再現しなかった」だけで「安全」ではありません。**CIゲートは継続運用の"仕組み化"であり、人間のレビューと脅威モデリングを補完するもの——置き換えるものではありません。**

この線引きを踏まえると、CIゲートの正しい役割が見えます。**人間の貴重なレビュー時間を、機械が潰せる退屈な穴の指摘ではなく、本当に難しい設計判断に集中させる**こと。ノイズを消し、確証だけを赤にするのは、まさにそのためです。

なお、ここで扱った「PRごとのスキャン」を超えて、**継続的なCI認可チェック**（チームのリポジトリに対する常時監視や、認可設計の継続的な検証）まで踏み込む領域は、提供準備中の上位プラン「Aegis Team」で扱う構想です。関心があれば[先行登録（ウェイトリスト）](/contact?type=product-waitlist)から。現時点のOSS版（`npx @aegiskit/cli scan`）でも、本記事のCIゲートは今日から組めます。

---

## 11. CI導入チェックリスト

CIセキュリティゲートを「死なないゲート」として組むための最小確認項目です。

- [ ] **PRとpushの両方**でスキャンが走る（`on: [push, pull_request]`）
- [ ] `permissions` は **`contents: read` + `security-events: write`** の最小権限
- [ ] SARIFアップロードに **`if: always()`** を付け、赤でも指摘が必ず載る
- [ ] 結果が **コードスキャニングのSecurityタブ**と**PR差分の注釈**に出ている
- [ ] **段階1（warn）から開始**し、ベースラインを片付けてから block に昇格
- [ ] ブロックするのは **confirmed（高確度・動的再現済み）だけ**——ノイズは warning に留める
- [ ] block 昇格後、**ブランチ保護**で `Security` チェックをマージ必須に指定
- [ ] CIから外部認証が要るなら **OIDC（短命トークン）**——長期キーをSecretsに置かない
- [ ] Action参照は **`tomodahinata/aegis@main`**（必要なら**コミットSHAでピン留め**）
- [ ] RLSの退行を **pgTAP回帰テスト**で同じCIに束ねている
- [ ] **CIが緑＝安全ではない**ことをチームが理解している（設計レビューは別途）

発注者・チームリードの視点で最も効く問いは、**「セキュリティチェックはCIに入っていますか？」「赤が出たとき、それは"本当にヤバい"と信用されていますか？」**の2つです。後者に「いや、ノイズが多くて…」と返ってくるなら、ゲートは事実上死んでいます。

---

## まとめ：仕組みで止め、確証だけを赤にする

要点を整理します。

- セキュリティ検査は**PRの時点でCIに入れて初めて意味を持つ**。本番後に探すのではなく、マージ前に止める（シフトレフト）。価値の源泉は「人の規律」ではなく「構造での強制」。
- **SARIF**は静的解析結果の標準フォーマット。これに統一すれば、**GitHubコードスキャニングのSecurityタブと、PR差分の行単位注釈**に結果が載り、ツールを乗り換えてもCI連携は不変。
- GitHub Actionsでの実装は短い。**`permissions: security-events: write` + `contents: read`** の最小権限、`if: always()` でのSARIFアップロード、`severity` での閾値指定が骨格。Action参照は **`tomodahinata/aegis@main`**。
- CIゲートの最大の敵は**誤検知**。ノイズだらけのゲートは必ず迂回される。だから**「高確度のみブロック」**——静的の疑いを**動的プローブで再現できたもの（SAST×DAST相関）**だけを赤にする。
- 導入は**warn から始め、確証だけを block に昇格**。CIから外部認証が要るなら**OIDC（鍵レス）**で長期シークレットを排除。
- 正直に言えば、**CIゲートは設計レビュー・手動監査の代わりにはならない**。ツールは認可の"正しさ"を証明しない。これは継続運用の"仕組み化"であり、人間の判断を**補完**するもの。

AIで速く作ること自体は正しい。**速く作ったものを、漏らさず安全に固める仕組み**——その第一歩として、まずは[Aegis](/aegis)（無料OSS、`npx @aegiskit/cli scan`）でCIゲートを組んでみてください。確証だけが赤になる体験を作れば、セキュリティは「気をつけるもの」から「構造で守られるもの」に変わります。

それでも、認可・RLSの設計そのものの正しさや、既存アプリの継続的な堅牢化が必要であれば、機械では塞げない領域です。私自身、[サーバーレス決済プラットフォーム](/case-studies/payment-platform-reliability)でフルスタック開発と決済信頼性レイヤーを主導し、CI/CDと検証ゲートを実運用で設計してきました。どこまで自動化で固め、どこから人手のレビューが要るか——その線引きを含め、[セキュリティ監査](/aegis/audit)で承ります。

---

## よくある質問（FAQ）

**Q. まず何から始めればいいですか？**
A. 第4節のYAMLを `.github/workflows/security.yml` に置き、`continue-on-error: true`（段階1）で**警告のみ**から始めてください。これでPRに指摘が出るようになります。ノイズの傾向が掴めたら `continue-on-error` を外し、ブランチ保護でマージ必須にします。

**Q. SARIFは自分で書く必要がありますか？**
A. いいえ。SARIFは**ツールが自動生成**します。あなたが書くのは `upload-sarif` に `sarif_file` を渡すYAMLだけです。SARIFの構造を理解しておく価値はありますが、手書きはしません。

**Q. なぜ全部の検出でブロックしないのですか？**
A. 第5節のとおり、**誤検知でCIが頻繁に赤くなると、ゲートが信用されず必ず迂回される**からです。確証（高確度・動的再現済み）だけをブロックに限定し、残りは警告として可視化する——これがゲートを「生かす」唯一の現実的な設計です。

**Q. CIが緑なら、もう安全ですか？**
A. いいえ。CIゲートが見ているのはコードとSQLの"形"であって、認可の"正しさ"ではありません。緑は「よくある罠は踏んでいない」を意味するだけです。事業ルールやテナント帰属の妥当性は、人間の設計レビューでしか判断できません。CIゲートは監査を**補完**するもので、置き換えるものではありません。

**Q. 個人開発や小規模でもやるべきですか？**
A. むしろ小規模ほどコスパが高いです。YAMLは10数行、OSSなので費用ゼロ。`continue-on-error` で警告から始めれば、既存PRを止めずに導入できます。AIで素早く作ったアプリこそ、CIに番をさせる価値が大きいというのが実情です。

---

## 参考資料

- [GitHub Docs — Code scanning（SARIFのアップロードとSecurityタブ・差分注釈）](https://docs.github.com/en/code-security/code-scanning)
- [OASIS — SARIF v2.1.0（静的解析結果交換フォーマットの標準仕様）](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html)
- [OWASP — Application Security Verification Standard（ASVS、検証可能性で測るセキュリティ）](https://owasp.org/www-project-application-security-verification-standard/)
- [OWASP — Top 10（最頻出のWebアプリリスク）](https://owasp.org/www-project-top-ten/)
