# Webアプリ脆弱性診断のやり方【2026年版】— OWASP公式手法（Top 10:2025 / WSTG / ASVS）とZAP・SASTで自動化する実践ガイド

> Webアプリの脆弱性診断を『自分でできる範囲』から実践するハンズオン。OWASP Top 10:2025・WSTG v4.2・ASVS 5.0の公式手法に忠実に、SCA・SAST(Semgrep)・シークレットスキャン・DAST(ZAP)・CI/SARIF統合を実コードで解説し、自動化の限界（認可/IDOR・業務ロジック）と専門監査が要る境界線まで正直に示します。

- 公開日: 2026-06-28
- 著者: 友田 陽大
- タグ: セキュリティ, 脆弱性診断, OWASP, Next.js, DevSecOps
- URL: https://tomodahinata.com/blog/web-application-vulnerability-assessment-owasp-zap-sast-dast-guide
- カテゴリ: アプリ層セキュリティ
- 総合ガイド: https://tomodahinata.com/blog/nextjs-supabase-application-security-guide

## 要点

- 脆弱性診断は『自動化で網羅できる水平の穴』と『設計でしか守れない垂直のリスク』に分かれる。費用対効果が最大なのは、まず無料ツールで前者を一掃し、機械に判定できない後者だけを人手に回す順番
- 公式の三本柱は最新版で固定する：何を探すか＝OWASP Top 10:2025（A01 アクセス制御が依然1位／SSRF統合／サプライチェーン・例外処理が新設）、どう試すか＝WSTG v4.2、どこまで満たすか＝ASVS 5.0
- 自動診断は4層に分解できる：SCA（依存＝A03）／ SAST（静的＝A05ほか・Semgrep）／ シークレットスキャン ／ DAST（動的＝A01/A05・ZAP）。各ツールをOWASPカテゴリに対応づけて運用する
- ZAPは baseline（受動・本番でも安全）→ full（能動・テスト環境のみ）→ api（OpenAPI）の3段。`ghcr.io/zaproxy/zaproxy:stable` と Automation Framework・GitHub Action・SARIF でCIゲート化できる（ZAPは現在OWASPを離れCheckmarx体制だが、依然OSS・無料）
- ツールが原理的に見つけられないのは認可/IDOR・テナント分離・業務ロジック——『事業ルールの意味』に依存するため。ここから先は手動監査の領域で、本番リリース前・RFP・コンプライアンス対応が発注の目安

---

最初に結論から述べます。**脆弱性診断は「ツールで網羅的に潰せる水平の穴」と「設計レビューでしか塞げない垂直のリスク」に分かれます。** そして費用対効果が最も高いのは、この順番です——**まず無料の公式ツールで水平の穴を一掃し、機械には正しさを判定できない垂直のリスクだけを人手に回す。**

「脆弱性診断を外注したいが、何をどこまで自分でやれるのか分からない」「OWASPという言葉は聞くが、具体的なやり方が分からない」——本記事は、その手前にある **"自分でできる範囲"を、OWASPの公式手法に忠実なハンズオンで一気通貫**します。SCA・SAST・シークレットスキャン・DAST（ZAP）・CI統合まで、すべて実際に動くコードで示します。

先に一番大事なことを断っておきます。**どんな自動ツールも「完全に安全」を保証しません。** スキャンが満点でも、最も漏洩を起こす認可（アクセス制御）の欠陥は素通りし得ます。本記事は、その境界線——**どこまでが無料で自動化でき、どこから先が専門家の監査領域なのか**——を、できる限り正直に引きます。

> **本記事の対象読者：** Next.js / Supabase などモダンWebスタックの個人開発者・スタートアップの開発者・技術選定をする立場の方。コード例はNext.js（App Router）を題材にしますが、診断の考え方とツールはフレームワーク非依存です。

---

## 0. ⚠️ 最重要：診断を始める前の「許可」の確認

技術の前に、**法律と倫理**を必ず先に押さえてください。これを飛ばすと、善意の診断が違法行為になり得ます。

- **診断してよいのは、自分が管理する資産か、書面で明示的な許可を得た対象だけ。** 他人のサービスや、許可のない本番システムへスキャンを撃ってはいけません。
- **特にDAST（動的診断）の "active scan" は実際に攻撃リクエストを送ります。** 日本では[不正アクセス行為の禁止等に関する法律](https://elaws.e-gov.go.jp/document?lawid=411AC0000000128)等に抵触するリスクがあります。許可なき第三者への能動スキャンは絶対にしないでください。
- **クラウド上で診断する場合は、各社のテストポリシーを事前確認。** AWS / Google Cloud / Azure はそれぞれ「許容されるセキュリティテスト」の規定を公開しています。共有テナント環境（サーバーレス等）では、対象外サービスや帯域・DoS的負荷が制限されることがあります。
- **本番環境への能動スキャンは原則避け、ステージング/ローカルで行う。** 後述する ZAP の **baseline scan（受動）だけは本番でも安全**ですが、**full scan（能動）はテスト環境限定**と覚えてください。

この一線さえ守れば、以降のすべては「自分のアプリを、自分で安全に診る」ための正当な手順です。法律と倫理の境界を体系的に押さえたい方は、[ホワイトハッカーと法律【保存版】](/blog/ethical-hacker-law-japan-unauthorized-access-act-active-cyber-defense-disclosure-guide)（不正アクセス禁止法・能動的サイバー防御・脆弱性の正しい届け方）を併読してください。

---

## 1. 用語の整理 — 「脆弱性診断」は何で、何でないか

発注の見積もりや提案の妥当性を判断するために、近い言葉との違いを押さえておきましょう。

| 用語 | 主な問い | やり方 | 本記事での扱い |
|---|---|---|---|
| **脆弱性診断（VA）** | 既知の脆弱性は残っていないか | ツール中心のスキャン（広く・自動） | **本記事の主題**。自分で実施できる |
| **ペネトレーションテスト** | 攻撃者は実際に侵入できるか | 攻撃者視点の手動攻撃（深く・狭く・実証） | 専門家領域。本記事では触り |
| **セキュリティ監査** | 設計と実装は「正しく」できているか | 自動検出＋設計レビュー＋是正設計 | 垂直リスク。第9節で境界を示す |
| **コードレビュー** | この変更に欠陥はないか | 人手の読み込み（範囲＝差分/PR） | 日常の品質担保 |

脆弱性診断（Vulnerability Assessment）は、**「既知の脆弱性パターンが残っていないかを、網羅的・反復的に洗い出す」**活動です。ツールで自動化できる部分が大きく、だからこそ**まず自分でやるべき**領域です。一方、ペネトレーションテストや監査は「人間の創造性・設計理解」を必要とするため、コストが高く、回す頻度も限られます。

> 「監査とは何か／いつ必要か／費用感」は別記事の[セキュリティ監査は何を見るのか — 自動化で足りる範囲と、監査が要る範囲](/blog/nextjs-supabase-security-audit-scope-when-needed-guide)で詳説しています。本記事はその手前、**"自動診断の実装"に全振り**します。

---

## 2. OWASP公式の「三本柱」を最新版で固定する

脆弱性診断の世界標準は [OWASP（Open Worldwide Application Security Project）](https://owasp.org/) が無償で公開しています。実務で参照すべきは、役割の異なる**3つの公式ドキュメント**です。これを混同せず使い分けるのが、診断を体系化する第一歩です。

| 公式ドキュメント | 答える問い | 最新版（2026年6月時点） | 使う場面 |
|---|---|---|---|
| **OWASP Top 10** | **何を**探すか（リスクの優先順位） | **Top 10:2025**（2026年1月正式版） | 診断の対象範囲を決める |
| **OWASP WSTG** | **どう**試すか（テスト手順） | **v4.2**（v5.0は策定中） | 各項目の具体的テスト方法 |
| **OWASP ASVS** | **どこまで**満たすか（達成基準） | **v5.0**（2025年5月公開） | 要件定義・受け入れ基準 |

「Top 10で当たりをつけ → WSTGで手を動かし → ASVSで合否を測る」という流れで使います。

### 2-1. OWASP Top 10:2025 — 何が変わったか

2026年1月に正式版が出た [OWASP Top 10:2025](https://owasp.org/Top10/2025/) は、175,000件超のCVEと248のCWEを分析した最新の**「最も重大なWebアプリリスクTOP10」**です。2021年版からの変化点を正しく把握しておきましょう。

| 順位 | カテゴリ（2025） | 2021からの変化 |
|---|---|---|
| **A01** | **Broken Access Control（アクセス制御の不備）** | 1位を維持。**SSRFがここに統合**された |
| **A02** | **Security Misconfiguration（セキュリティ設定ミス）** | 5位 → **2位に上昇** |
| **A03** | **Software Supply Chain Failures（ソフトウェアサプライチェーンの不備）** | 「脆弱な依存」から**範囲を拡大・改名** |
| **A04** | Cryptographic Failures（暗号の不備） | 下降 |
| **A05** | Injection（インジェクション） | 下降。XSSを含む |
| **A06** | Insecure Design（安全でない設計） | 維持 |
| **A07** | **Authentication Failures（認証の不備）** | 「識別と認証の不備」から改名 |
| **A08** | Software or Data Integrity Failures（完全性の不備） | 維持 |
| **A09** | **Security Logging & Alerting Failures（ログと"通知"の不備）** | "Monitoring"から"Alerting"へ強調点が移動 |
| **A10** | **Mishandling of Exceptional Conditions（例外条件の取り扱い不備）** | **完全に新設**（エラー処理・異常系） |

**実務上のポイント：**

- **A01が依然1位**であり、**SSRFがA01に統合**された点が今回の最大の構造変化です。後述しますが、A01の中核である**認可（IDOR/BOLA）は自動ツールが最も苦手とする領域**——ここに垂直リスクの本丸があります。
- **A03がサプライチェーン全体に拡大**したことで、依存ライブラリだけでなく**ビルドパイプライン・CI/CDの整合性**まで診断対象に含まれます。後述のSCA＋シークレットスキャンで土台を固めます。
- **A10（例外条件）が新設**。エラー処理の漏れ・異常系での情報露出が独立カテゴリになりました。型安全な言語・厳格なエラーハンドリングが効きます。

### 2-2. OWASP WSTG v4.2 — テストの「やり方」の地図

[Web Security Testing Guide v4.2](https://owasp.org/www-project-web-security-testing-guide/v42/) は、**実際のテスト手順を網羅した公式の手引書**です。Top 10が「何を」なら、WSTGは「どうやって」を、次の12カテゴリ（4.1〜4.12）で示します。

1. **4.1 Information Gathering**（情報収集）
2. **4.2 Configuration and Deployment Management Testing**（設定・デプロイ管理）
3. **4.3 Identity Management Testing**（ID管理）
4. **4.4 Authentication Testing**（認証）
5. **4.5 Authorization Testing**（**認可** — IDORはここ）
6. **4.6 Session Management Testing**（セッション管理）
7. **4.7 Input Validation Testing**（入力検証 — SQLi/XSS/SSRFはここ）
8. **4.8 Testing for Error Handling**（エラー処理）
9. **4.9 Testing for Weak Cryptography**（暗号）
10. **4.10 Business Logic Testing**（**業務ロジック**）
11. **4.11 Client-side Testing**（クライアントサイド — DOM XSSはここ）
12. **4.12 API Testing**（API）

このうち**4.1〜4.4・4.6〜4.9・4.11・4.12の大半は自動化で網羅できます**。一方、**4.5（認可）と4.10（業務ロジック）は機械化が極めて難しい**——この2つが本記事後半で扱う「垂直リスク」です。診断を設計するときは、まずこの地図の上に「自動で塗れる範囲」と「手で塗る範囲」を色分けする、と考えると整理できます。

### 2-3. OWASP ASVS 5.0 — 「どこまでやれば合格か」の物差し

[ASVS（Application Security Verification Standard）5.0](https://owasp.org/www-project-application-security-verification-standard/)（2025年5月公開）は、約350の検証要件を17章にまとめた**「達成基準のチェックリスト」**です。3つのレベルで段階的に適用します。

- **L1（基本）**: すべてのアプリが満たすべき最低限。**外部からのブラックボックス診断で検証できる範囲**。
- **L2（標準）**: 機密データを扱う多くのアプリの推奨水準。個人情報・課金を扱うSaaSはここが目標。
- **L3（高度）**: 医療・金融・重要インフラなど、最高水準を要する系。

実務では「自社プロダクトは **ASVS L2を満たす** ことを目標にする」のように、**受け入れ基準（Definition of Done）に組み込む**のが効果的です。診断は「穴を見つける」作業、ASVSは「埋まったかを測る」物差し、と役割分担します。

> **【最新・公式の注意】DASTの定番ツール「ZAP」はもうOWASPプロジェクトではありません。** 後述する [ZAP（Zed Attack Proxy）](https://www.zaproxy.org/) は、長らく "OWASP ZAP" と呼ばれてきましたが、**2023年にOWASPを離れ、2024年9月からはCheckmarx体制（"ZAP by Checkmarx"）**で開発が続いています。**ライセンスは引き続きOSS（Apache-2.0相当）・無料**で、機能・コミュニティ運営は維持されています。古い記事の `owasp/zap2docker-stable` というDockerイメージ名も**廃止済み**で、現在は `ghcr.io/zaproxy/zaproxy:stable` です。ここを間違えると動きません。

---

## 3. 診断の全体像 — 自動診断を「4レイヤー」に分解する

自動でできる脆弱性診断は、**観測する対象が違う4種類のツール**に分解できます。それぞれが OWASP Top 10:2025 のどこを潰すのかを対応づけたのが次の表です。**この対応づけが、本記事で一番持ち帰ってほしい「地図」**です。

| レイヤー | ツール種別 | 何を見るか | 主に潰すOWASP Top 10:2025 | 代表ツール（無料） |
|---|---|---|---|---|
| **依存** | **SCA** | `package.json`/lockfile | **A03**（サプライチェーン） | `npm audit`, Dependabot, OSV-Scanner |
| **コード** | **SAST** | ソースのデータフロー | **A05**（注入）, A04, A02 | Semgrep, CodeQL |
| **秘密情報** | **シークレットスキャン** | リポジトリ・差分 | **A02/A03**, A04 | Gitleaks, GitHub Secret Scanning |
| **動作** | **DAST** | 動いているアプリへの実リクエスト | **A01/A05/A02** | **ZAP**, Nuclei |

4つを**「速い順・浅い順」**に並べると、CIに組み込む順序が見えてきます。

```
[PR毎・秒〜分]  SCA → シークレットスキャン → SAST    （静的・速い・本番不要）
[夜間・リリース前・分〜時]  DAST baseline → DAST full    （動的・遅い・要デプロイ）
```

この順序で「速くて安いものから先に回す」のがDevSecOpsの定石です。以下、レイヤーごとに**実際に動くコマンドとコード**を示します。

---

## 4. ハンズオン① SCA — 依存ライブラリの既知脆弱性（A03）

最も安く・最も効果が高いのがここです。あなたのコードが完璧でも、**`node_modules` の中に既知のCVEが眠っていれば穴**になります。A03（サプライチェーン）が2025で格上げされた今、最優先です。

### 4-1. `npm audit` — まず1コマンド

```bash
# 既知脆弱性のある依存を検出（lockfileベース、ネットワーク不要のオフライン照合）
npm audit

# CIで使う：moderate以上が1件でもあれば exit code 1 で落とす
npm audit --audit-level=moderate

# 自動修正（メジャー更新を伴わない安全な範囲のみ）
npm audit fix

# 機械処理用にJSONで取得
npm audit --json > npm-audit.json
```

`--audit-level` の閾値を `moderate` にしておくと、**「中程度以上の脆弱性が混入したPRは自動でブロック」**できます。これだけでA03の大半は塞げます。

### 4-2. Dependabot — 「検出」から「自動PR」へ

検出して終わりでは運用が回りません。[GitHub Dependabot](https://docs.github.com/en/code-security/dependabot) を有効化すると、**脆弱な依存に対して修正PRを自動生成**してくれます。リポジトリに次の1ファイルを置くだけです。

```yaml
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    # セキュリティ修正は即時、通常更新は週次にまとめてノイズを抑える
    open-pull-requests-limit: 10
    groups:
      # マイナー/パッチをまとめて1PRに（レビュー負荷を下げる）
      minor-and-patch:
        update-types: ["minor", "patch"]
```

> **コスト効率のヒント：** SCAは無料エコシステム標準（`npm audit` + Dependabot）で十分です。**有料の脆弱性診断にSCAを含める必要はありません。** ここを自動化で割り切り、人手の予算は第9節の垂直リスクに回すのが正解です。

---

## 5. ハンズオン② SAST — ソースコードの静的解析（A05ほか）

SAST（Static Application Security Testing）は、**コードを実行せずにデータフローを追い**、未サニタイズの入力がSQLや`dangerouslySetInnerHTML`に流れ込む経路などを検出します。SQLi・コマンドインジェクション・パストラバーサル・オープンリダイレクト・DOM XSS（A05）に強い層です。

無料で実用的なのが [Semgrep](https://semgrep.dev/) です。OWASP公式のルールセットも提供されています。

### 5-1. Semgrep — まず1コマンドで全体を診る

```bash
# インストール不要で実行（自動でスタックを判定し最適ルールを適用）
npx semgrep scan --config=auto

# OWASP Top 10 特化ルールセットで診る
npx semgrep scan --config=p/owasp-top-ten

# 複数ルールセットを束ねて、結果をSARIFで出力（GitHub連携用）
npx semgrep scan \
  --config=p/owasp-top-ten \
  --config=p/javascript \
  --config=p/typescript \
  --config=p/react \
  --config=p/secrets \
  --sarif --output=semgrep.sarif
```

`p/owasp-top-ten` は **OWASP Top 10 の各カテゴリに対応するルール集**で、何が「どのリスクに対応するか」がルールにタグ付けされています。診断結果を Top 10 にマッピングして報告する際にそのまま使えます。

### 5-2. SASTが拾う典型例 — 修正前後

SASTは「構造的欠陥」を見ます。たとえば次のNext.js Route Handlerは、**ユーザー入力をSQLに文字列連結**しており、Semgrepが高確度で警告します（A05 / WSTG 4.7）。

```ts
// ❌ Bad: 入力を直接連結 → SQLインジェクション（Semgrepが検出）
// app/api/search/route.ts
import { sql } from "@/lib/db";

export async function GET(request: Request) {
  const q = new URL(request.url).searchParams.get("q") ?? "";
  // q = "'; DROP TABLE users; --" で破滅する
  const rows = await sql(`SELECT id, title FROM posts WHERE title LIKE '%${q}%'`);
  return Response.json(rows);
}
```

```ts
// ✅ Good: パラメータ化クエリ＋境界でのZod検証（A05/A10を同時に塞ぐ）
// app/api/search/route.ts
import { z } from "zod";
import { sql } from "@/lib/db";

const SearchQuery = z.object({
  q: z.string().trim().min(1).max(100),
});

export async function GET(request: Request) {
  const parsed = SearchQuery.safeParse(
    Object.fromEntries(new URL(request.url).searchParams),
  );
  // 入力検証の失敗を「異常系」として明示的に処理（A10対策）
  if (!parsed.success) {
    return Response.json({ error: "invalid query" }, { status: 400 });
  }
  // プレースホルダで値を渡す＝SQLとデータを分離（A05対策）
  const rows = await sql`SELECT id, title FROM posts WHERE title LIKE ${"%" + parsed.data.q + "%"}`;
  return Response.json(rows);
}
```

ポイントは、**SASTの警告を「パラメータ化＋境界での型検証」という設計で根治する**ことです。場当たりのエスケープではなく、**信頼境界（システムの外から来る値）で必ず検証・無害化する**——これがOWASPの一貫した思想であり、A05・A07・A10をまとめて下げます。

### 5-3. 誤検知（False Positive）との付き合い方

SASTは**過検出**しがちです。確実に安全と判断した箇所は、根拠コメントを添えて抑制します。野放図な抑制は禁物——**「なぜ安全か」を1行で説明できないなら、それは抑制ではなく未対応**です。

```ts
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring
// 理由: `template` は環境変数由来の固定値で、ユーザー入力は混入しない（境界で検証済み）
const message = format(template, userName);
```

---

## 6. ハンズオン③ シークレットスキャン — 鍵の混入（A02/A03）

`NEXT_PUBLIC_` の付け間違いや、`.env` のコミット、デバッグ中のハードコードで、**APIキーやDB接続文字列がリポジトリに漏れる**のは典型的な事故です。これはA02（設定ミス）かつA03（サプライチェーン）の入口になります。

二段構えで防ぎます。

1. **流出を「履歴から」検出** — [Gitleaks](https://github.com/gitleaks/gitleaks)（OSS）で全履歴を走査。
2. **流出を「push前に」止める** — [GitHub Secret Scanning の Push Protection](https://docs.github.com/en/code-security/secret-scanning/about-push-protection) を有効化。コミットに秘密が含まれると**pushがブロック**される。

```yaml
# .github/workflows/secret-scan.yml
name: secret-scan
on: [push, pull_request]
jobs:
  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # 全履歴を走査するため必須
      - uses: gitleaks/gitleaks-action@v2
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

> **設計で根治する：** そもそも秘密がコードに「書けない」構造にするのが本筋です。サーバー専用の秘密は `process.env` 経由でサーバーコンポーネント／Route Handlerだけが触れるようにし、**クライアントに渡る値（`NEXT_PUBLIC_*`）と型レベルで分離**します。env境界をZodで検証する設計は、別記事の[環境変数の漏洩を防ぐ設計](/blog/nextjs-env-secret-leak-prevention-public-vars-guide)で扱っています。

---

## 7. ハンズオン④ DAST — ZAPで「動いているアプリ」を攻撃視点で診る（A01/A05/A02）

ここが脆弱性診断の華、**DAST（Dynamic Application Security Testing）**です。SASTがソースを読むのに対し、DASTは**実際に動いているアプリへHTTPリクエストを送り**、外から見える挙動で脆弱性を判定します。反射XSS・オープンリダイレクト・SQLi（真偽/エラー推論）・セキュリティヘッダー欠落・SSRFなどに強い層です。

無料の定番が [ZAP（Zed Attack Proxy）](https://www.zaproxy.org/) です。前述の通り**現在はCheckmarx体制ですが、OSS・無料は維持**されています。Dockerで使うのが最も再現性が高く、3種類のスキャンを使い分けます。

| スキャン | スクリプト | 動作 | 本番で撃ってよいか |
|---|---|---|---|
| **Baseline** | `zap-baseline.py` | 既定1分のスパイダー＋**受動**スキャン（攻撃しない） | **✅ 安全**（CI/本番でも可） |
| **Full** | `zap-full-scan.py` | フルスパイダー＋**能動**スキャン（実際に攻撃する） | **❌ テスト環境限定** |
| **API** | `zap-api-scan.py` | OpenAPI/GraphQL定義をもとにAPIを能動診断 | **❌ テスト環境限定** |

### 7-1. Baseline scan — 本番でも安全な「受動」診断から

まずは攻撃を撃たない baseline から。**セキュリティヘッダーの欠落・混在コンテンツ・Cookie属性**など、受動的に分かる問題を1分で洗います。

```bash
# 受動スキャン（攻撃リクエストを送らないので本番URLにも安全）
# HTMLとJSONのレポートを出力（カレントをマウント）
docker run --rm -v "$(pwd):/zap/wrk/:rw" \
  -t ghcr.io/zaproxy/zaproxy:stable \
  zap-baseline.py \
    -t https://staging.example.com \
    -r zap-baseline-report.html \
    -J zap-baseline-report.json \
    -m 3                 # スパイダーに3分かける
```

**終了コードでCIを制御**できます（ここがCI統合の肝）。

| 終了コード | 意味 | CIでの扱い |
|---|---|---|
| `0` | 警告なし（成功） | パス |
| `1` | 失敗（FAILレベルの検出あり） | ブロック |
| `2` | 警告のみ | 運用に応じて判断 |
| `3` | その他のエラー | 設定を見直す |

### 7-2. Full scan — 能動診断は「自分のテスト環境」だけに

ローカルやステージングに対しては、実際に攻撃ペイロードを送る full scan で深く診ます。**本番には絶対に撃たないでください**（第0節）。

```bash
# 能動スキャン：実際にSQLi/XSS等のペイロードを送る。テスト環境限定。
docker run --rm -v "$(pwd):/zap/wrk/:rw" \
  -t ghcr.io/zaproxy/zaproxy:stable \
  zap-full-scan.py \
    -t http://host.docker.internal:3000 \
    -r zap-full-report.html
```

### 7-3. API scan — OpenAPI定義からAPIを網羅

画面を持たないAPIは、スパイダーでは辿れません。**OpenAPI(Swagger)/GraphQLの定義を食わせて**、エンドポイントを網羅的に診ます。

```bash
# OpenAPI 定義をもとにAPIを能動診断
docker run --rm -v "$(pwd):/zap/wrk/:rw" \
  -t ghcr.io/zaproxy/zaproxy:stable \
  zap-api-scan.py \
    -t http://host.docker.internal:3000/api/openapi.json \
    -f openapi \
    -r zap-api-report.html
```

### 7-4. Automation Framework — 複雑な診断を1枚のYAMLで宣言的に

baseline / full の各スクリプトは便利ですが、**認証が必要なアプリ**や**スキャン手順を細かく制御したい**場合は、ZAP公式が推奨に切り替えつつある [Automation Framework](https://www.zaproxy.org/docs/automate/automation-framework/) を使います。**スキャン計画を1枚の `zap.yaml` に宣言的に書く**ので、再現性・可読性・バージョン管理性が段違いです。

```yaml
# zap.yaml — 環境定義 + ジョブ列をコードとして宣言（公式のジョブ型に準拠）
env:
  contexts:
    - name: "my-app"
      urls: ["http://host.docker.internal:3000"]
      includePaths: ["http://host.docker.internal:3000.*"]
      # 認証が要るアプリはここに認証コンテキストを定義（form/json/script等）
  parameters:
    failOnError: true
    progressToStdout: true

jobs:
  - type: passiveScan-config       # 受動スキャンの設定
    parameters: { maxAlertsPerRule: 10 }
  - type: spider                   # 従来型スパイダーでURLを収集
    parameters: { context: "my-app", maxDuration: 5 }
  - type: spiderAjax               # SPA(React)はAjaxスパイダーが必須
    parameters: { context: "my-app", maxDuration: 5 }
  - type: passiveScan-wait         # 受動スキャンの完了を待つ
  - type: activeScan               # 能動スキャン（テスト環境限定）
    parameters: { context: "my-app" }
  - type: report                   # SARIFで出力 → GitHub Code Scanningへ
    parameters:
      template: "sarif-json"
      reportDir: "/zap/wrk/"
      reportFile: "zap-report"
```

```bash
# Automation Framework を実行
docker run --rm -v "$(pwd):/zap/wrk/:rw" \
  -t ghcr.io/zaproxy/zaproxy:stable \
  zap.sh -cmd -autorun /zap/wrk/zap.yaml
```

> **SPA（React/Next.js）診断の落とし穴：** クライアントレンダリングが主のアプリは、従来型スパイダーだとリンクを辿れず**カバレッジが激減**します。`spiderAjax`（Ajaxスパイダー）を必ず併用してください。これを知らずに「ZAPは何も見つけない＝安全」と誤認するのが、独学DASTの最頻出ミスです。

---

## 8. ハンズオン⑤ CI/CD統合 — 4レイヤーを「自動ゲート」にする

ここまでの4レイヤーを**GitHub Actionsで一本のパイプライン**にまとめ、**結果をSARIFでGitHub Code Scanningに集約**します。これで、脆弱性は「PRのたびに自動で可視化・ブロック」され、**可観測性・回復性・冪等性**を備えた本番運用品質の診断ゲートになります。

```yaml
# .github/workflows/security.yml
name: security
on:
  pull_request:
  push:
    branches: [main]
  schedule:
    - cron: "0 18 * * *" # 毎晩（UTC）にDAST baselineも回す

# SARIFアップロードに必要な最小権限（最小権限の原則）
permissions:
  contents: read
  security-events: write

jobs:
  # --- 静的：速い・本番不要。PR毎に回す ---
  sca:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: "24" }
      - run: npm ci
      - run: npm audit --audit-level=moderate

  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Semgrep (SARIF)
        run: |
          npx semgrep scan \
            --config=p/owasp-top-ten --config=p/typescript --config=p/react \
            --sarif --output=semgrep.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: semgrep.sarif }

  secrets:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: gitleaks/gitleaks-action@v2
        env: { GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" }

  # --- 動的：遅い・要デプロイ。夜間とリリース前だけ回す ---
  dast-baseline:
    if: github.event_name == 'schedule'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: ZAP Baseline Scan
        uses: zaproxy/action-baseline@v0.15.0
        with:
          target: "https://staging.example.com"
          # 誤検知を抑制するルールファイル（理由付きで管理）
          rules_file_name: ".zap/rules.tsv"
          # 警告だけでジョブを失敗させない運用も可（cmd_options: '-I'）
```

ポイントは**「静的＝PR毎、動的＝夜間/リリース前」という頻度の出し分け**です。DASTは遅く要デプロイなので毎PRには重い。速いものを高頻度・遅いものを低頻度で回すことで、**フィードバックの速さとコストを両立**します。SAST/secretの結果はSARIFでGitHubの「Security」タブに集約され、**回帰（同じ穴の再混入）を恒久的に防ぐ仕組み**になります。

CI統合をSARIF前提でさらに深掘りしたい場合は、別記事の[セキュリティ診断をCIに組み込む（SARIF × GitHub Actions）](/blog/nextjs-supabase-security-ci-sarif-github-actions-guide)を参照してください。

---

## 9. 自動診断の限界 — ツールが「原理的に」見つけられないもの

ここまでで、OWASP Top 10:2025 の **A02〜A05・A08・A10 の大半は自動で塞げました**。しかし——**スキャンが全部グリーンでも、それは「安全」ではありません。**

自動ツールが**原理的に**見つけられない脆弱性があります。それは **A01（アクセス制御）の中核＝認可** と **A06/業務ロジック（WSTG 4.5 / 4.10）** です。

### なぜ機械には判定できないのか

次のコードを見てください。これは**構文的に完璧**で、SAST・DASTのどちらも警告を出しません。しかし**重大なIDOR（Broken Object Level Authorization）**です。

```ts
// ❌ 認可の欠落：ツールは「正しいコード」と判定するが、他人の請求書が見える
// app/api/invoices/[id]/route.ts
export async function GET(_req: Request, { params }: { params: { id: string } }) {
  // ログイン済みかは確認している…が、「その請求書が"自分のもの"か」を確認していない
  const invoice = await db.invoice.findUnique({ where: { id: params.id } });
  return Response.json(invoice); // 他人のIDを入れれば、他人の請求書が返る
}
```

```ts
// ✅ 所有権を必ず検証する（リソースを"誰が所有するか"はアプリ固有の事業ルール）
export async function GET(req: Request, { params }: { params: { id: string } }) {
  const userId = await requireUserId(req); // 認証
  const invoice = await db.invoice.findFirst({
    where: { id: params.id, ownerId: userId }, // 認可：所有権でフィルタ
  });
  if (!invoice) return Response.json({ error: "not found" }, { status: 404 });
  return Response.json(invoice);
}
```

**ツールが警告を出せない理由は明快です。** 「`invoice` を `userId` でフィルタすべきだ」という知識は、**あなたの事業ルール（誰が何を所有し、どの操作を許されるか）の"意味"**に依存します。スキャナはあなたのデータモデルを知らないので、**この欠落を「欠落」だと判定できない**のです。SQLインジェクションが「どんなアプリでも同じ構造的欠陥」なのとは対照的です。

このオブジェクト単位の認可欠陥（IDOR/BOLA）は、[OWASP API Security Top 10](https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/) でも**初版以来ずっと1位**——「めったに起きない高度な攻撃」ではなく「最も普通に起きる漏洩」です。

| 自動診断で塞げる（水平の穴） | 自動診断では塞げない（垂直のリスク） |
|---|---|
| インジェクション（A05）、ヘッダー/設定（A02） | **認可/IDOR（A01）** |
| 既知の脆弱な依存（A03） | **テナント分離・データ越境** |
| 秘密情報の混入、暗号設定（A04） | **業務ロジックの悪用**（数量・価格・状態遷移） |
| 既知パターンの欠陥 | **権限昇格・設計の妥当性** |

Next.js × Supabase でのIDOR/認可の検出と、RLS（行レベルセキュリティ）による多層防御は、別記事の[IDOR・認可欠陥の検出（RLSとの二重化）](/blog/nextjs-supabase-idor-broken-authorization-rls-detection-guide)で具体的に扱っています。

---

## 10. 運用設計 — どの場面で何を、どの頻度で回すか

「やり方」が分かったら、最後は**継続できる運用に落とし込む**ことが重要です。一度きりの診断は、翌週のデプロイで陳腐化します。次の頻度設計を推奨します。

| タイミング | 回すもの | 目的 |
|---|---|---|
| **コミット/push前** | シークレットスキャン（Push Protection） | 秘密の流出を物理的に止める |
| **PR毎** | SCA・SAST | 新しい穴の混入を即ブロック（回帰防止） |
| **夜間（nightly）** | DAST baseline（ステージング） | 受動診断で設定崩れを毎日検知 |
| **リリース前** | DAST full / API scan | 能動診断で深く確認 |
| **四半期 / 大型リリース前** | **手動の認可レビュー・監査** | 垂直リスク（IDOR・業務ロジック） |
| **依存更新時** | SCA 再実行 + Dependabot | サプライチェーンの追従 |

**費用の考え方：** 上表の**上から5行目まではすべて無料ツールで内製化**できます。お金を払うべきは最下段、**機械に判定できない垂直リスクの監査だけ**です。全部を人手でやればコストが膨らみ、全部をツールに任せれば最も深刻な認可が素通りします。**「無料で水平を一掃 → 垂直だけ有償」**が、最も安く・最も安全な予算配分です。

### 専門家の監査を発注すべきサイン

以下に当てはまるなら、自動診断を土台にしつつ、**垂直リスクの手動監査**を検討する局面です。

- **エンタープライズ／RFP・大型契約**で、セキュリティ要件（ASVS L2相当）の充足を示す必要がある
- **SOC2 / ISO 27001 / Pマーク**などコンプライアンス対応
- **資金調達のデューデリジェンス**でセキュリティ体制を問われる
- **インシデント後**で、原因の体系的な洗い出しと再発防止が要る
- **AIで大量にコード生成した直後**のリリース前（認可・業務ロジックの抜けが入りやすい）

---

## まとめ — 「自分でできる範囲」を最大化し、その先だけを人に頼む

本記事の地図をもう一度たどります。

1. **許可の確認が最優先**。自分の資産か書面の許可がある対象だけ。能動スキャン（DAST full）は本番に撃たない。
2. **公式の三本柱を最新版で固定**：何を探すか＝**Top 10:2025**、どう試すか＝**WSTG v4.2**、どこまで満たすか＝**ASVS 5.0**。
3. **自動診断は4レイヤー**：**SCA**（A03）→ **シークレットスキャン** → **SAST**（A05）→ **DAST/ZAP**（A01/A05/A02）。速い順にCIへ。
4. **CI/SARIFでゲート化**。静的＝PR毎、動的＝夜間/リリース前。回帰を恒久的に防ぐ。
5. **ツールの限界を正直に**。認可/IDOR・テナント分離・業務ロジックは機械に判定できない。ここが監査の領域。

脆弱性診断は「専門家に丸投げするもの」ではありません。**水平の穴は、無料の公式ツールで開発者自身が一掃できます。** そのうえで、機械に判定できない垂直リスクだけにお金と人の時間を使う——これが、限られた予算で本番品質のセキュリティを担保する、最も合理的な戦い方です。

私自身、経済産業大臣賞を受賞したB2B SaaSや、本番二重課金0件を達成した決済基盤を、**一人 × 生成AI** で設計・実装してきました。その過程で固めた「自動化で速く・安く守り、設計判断は人間が握る」ワークフローを、本記事のすべてのコードに落とし込んでいます。まずは `npx semgrep scan --config=auto` の1コマンドから、あなたのアプリを今日診てみてください。
