最初に結論から述べます。脆弱性診断は「ツールで網羅的に潰せる水平の穴」と「設計レビューでしか塞げない垂直のリスク」に分かれます。 そして費用対効果が最も高いのは、この順番です——まず無料の公式ツールで水平の穴を一掃し、機械には正しさを判定できない垂直のリスクだけを人手に回す。
「脆弱性診断を外注したいが、何をどこまで自分でやれるのか分からない」「OWASPという言葉は聞くが、具体的なやり方が分からない」——本記事は、その手前にある "自分でできる範囲"を、OWASPの公式手法に忠実なハンズオンで一気通貫します。SCA・SAST・シークレットスキャン・DAST(ZAP)・CI統合まで、すべて実際に動くコードで示します。
先に一番大事なことを断っておきます。どんな自動ツールも「完全に安全」を保証しません。 スキャンが満点でも、最も漏洩を起こす認可(アクセス制御)の欠陥は素通りし得ます。本記事は、その境界線——どこまでが無料で自動化でき、どこから先が専門家の監査領域なのか——を、できる限り正直に引きます。
本記事の対象読者: Next.js / Supabase などモダンWebスタックの個人開発者・スタートアップの開発者・技術選定をする立場の方。コード例はNext.js(App Router)を題材にしますが、診断の考え方とツールはフレームワーク非依存です。
0. ⚠️ 最重要:診断を始める前の「許可」の確認
技術の前に、法律と倫理を必ず先に押さえてください。これを飛ばすと、善意の診断が違法行為になり得ます。
- 診断してよいのは、自分が管理する資産か、書面で明示的な許可を得た対象だけ。 他人のサービスや、許可のない本番システムへスキャンを撃ってはいけません。
- 特にDAST(動的診断)の "active scan" は実際に攻撃リクエストを送ります。 日本では不正アクセス行為の禁止等に関する法律等に抵触するリスクがあります。許可なき第三者への能動スキャンは絶対にしないでください。
- クラウド上で診断する場合は、各社のテストポリシーを事前確認。 AWS / Google Cloud / Azure はそれぞれ「許容されるセキュリティテスト」の規定を公開しています。共有テナント環境(サーバーレス等)では、対象外サービスや帯域・DoS的負荷が制限されることがあります。
- 本番環境への能動スキャンは原則避け、ステージング/ローカルで行う。 後述する ZAP の baseline scan(受動)だけは本番でも安全ですが、full scan(能動)はテスト環境限定と覚えてください。
この一線さえ守れば、以降のすべては「自分のアプリを、自分で安全に診る」ための正当な手順です。法律と倫理の境界を体系的に押さえたい方は、ホワイトハッカーと法律【保存版】(不正アクセス禁止法・能動的サイバー防御・脆弱性の正しい届け方)を併読してください。
1. 用語の整理 — 「脆弱性診断」は何で、何でないか
発注の見積もりや提案の妥当性を判断するために、近い言葉との違いを押さえておきましょう。
| 用語 | 主な問い | やり方 | 本記事での扱い |
|---|---|---|---|
| 脆弱性診断(VA) | 既知の脆弱性は残っていないか | ツール中心のスキャン(広く・自動) | 本記事の主題。自分で実施できる |
| ペネトレーションテスト | 攻撃者は実際に侵入できるか | 攻撃者視点の手動攻撃(深く・狭く・実証) | 専門家領域。本記事では触り |
| セキュリティ監査 | 設計と実装は「正しく」できているか | 自動検出+設計レビュー+是正設計 | 垂直リスク。第9節で境界を示す |
| コードレビュー | この変更に欠陥はないか | 人手の読み込み(範囲=差分/PR) | 日常の品質担保 |
脆弱性診断(Vulnerability Assessment)は、「既知の脆弱性パターンが残っていないかを、網羅的・反復的に洗い出す」活動です。ツールで自動化できる部分が大きく、だからこそまず自分でやるべき領域です。一方、ペネトレーションテストや監査は「人間の創造性・設計理解」を必要とするため、コストが高く、回す頻度も限られます。
「監査とは何か/いつ必要か/費用感」は別記事のセキュリティ監査は何を見るのか — 自動化で足りる範囲と、監査が要る範囲で詳説しています。本記事はその手前、"自動診断の実装"に全振りします。
2. OWASP公式の「三本柱」を最新版で固定する
脆弱性診断の世界標準は OWASP(Open Worldwide Application Security Project) が無償で公開しています。実務で参照すべきは、役割の異なる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 は、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 は、実際のテスト手順を網羅した公式の手引書です。Top 10が「何を」なら、WSTGは「どうやって」を、次の12カテゴリ(4.1〜4.12)で示します。
- 4.1 Information Gathering(情報収集)
- 4.2 Configuration and Deployment Management Testing(設定・デプロイ管理)
- 4.3 Identity Management Testing(ID管理)
- 4.4 Authentication Testing(認証)
- 4.5 Authorization Testing(認可 — IDORはここ)
- 4.6 Session Management Testing(セッション管理)
- 4.7 Input Validation Testing(入力検証 — SQLi/XSS/SSRFはここ)
- 4.8 Testing for Error Handling(エラー処理)
- 4.9 Testing for Weak Cryptography(暗号)
- 4.10 Business Logic Testing(業務ロジック)
- 4.11 Client-side Testing(クライアントサイド — DOM XSSはここ)
- 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(2025年5月公開)は、約350の検証要件を17章にまとめた**「達成基準のチェックリスト」**です。3つのレベルで段階的に適用します。
- L1(基本): すべてのアプリが満たすべき最低限。外部からのブラックボックス診断で検証できる範囲。
- L2(標準): 機密データを扱う多くのアプリの推奨水準。個人情報・課金を扱うSaaSはここが目標。
- L3(高度): 医療・金融・重要インフラなど、最高水準を要する系。
実務では「自社プロダクトは ASVS L2を満たす ことを目標にする」のように、受け入れ基準(Definition of Done)に組み込むのが効果的です。診断は「穴を見つける」作業、ASVSは「埋まったかを測る」物差し、と役割分担します。
【最新・公式の注意】DASTの定番ツール「ZAP」はもうOWASPプロジェクトではありません。 後述する ZAP(Zed Attack Proxy) は、長らく "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コマンド
# 既知脆弱性のある依存を検出(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 を有効化すると、脆弱な依存に対して修正PRを自動生成してくれます。リポジトリに次の1ファイルを置くだけです。
# .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 です。OWASP公式のルールセットも提供されています。
5-1. Semgrep — まず1コマンドで全体を診る
# インストール不要で実行(自動でスタックを判定し最適ルールを適用)
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)。
// ❌ 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);
}
// ✅ 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行で説明できないなら、それは抑制ではなく未対応です。
// nosemgrep: javascript.lang.security.audit.unsafe-formatstring
// 理由: `template` は環境変数由来の固定値で、ユーザー入力は混入しない(境界で検証済み)
const message = format(template, userName);
6. ハンズオン③ シークレットスキャン — 鍵の混入(A02/A03)
NEXT_PUBLIC_ の付け間違いや、.env のコミット、デバッグ中のハードコードで、APIキーやDB接続文字列がリポジトリに漏れるのは典型的な事故です。これはA02(設定ミス)かつA03(サプライチェーン)の入口になります。
二段構えで防ぎます。
- 流出を「履歴から」検出 — Gitleaks(OSS)で全履歴を走査。
- 流出を「push前に」止める — GitHub Secret Scanning の Push Protection を有効化。コミットに秘密が含まれるとpushがブロックされる。
# .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で検証する設計は、別記事の環境変数の漏洩を防ぐ設計で扱っています。
7. ハンズオン④ DAST — ZAPで「動いているアプリ」を攻撃視点で診る(A01/A05/A02)
ここが脆弱性診断の華、DAST(Dynamic Application Security Testing)です。SASTがソースを読むのに対し、DASTは実際に動いているアプリへHTTPリクエストを送り、外から見える挙動で脆弱性を判定します。反射XSS・オープンリダイレクト・SQLi(真偽/エラー推論)・セキュリティヘッダー欠落・SSRFなどに強い層です。
無料の定番が ZAP(Zed Attack Proxy) です。前述の通り現在は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分で洗います。
# 受動スキャン(攻撃リクエストを送らないので本番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節)。
# 能動スキャン:実際に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の定義を食わせて、エンドポイントを網羅的に診ます。
# 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 を使います。スキャン計画を1枚の zap.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"
# 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のたびに自動で可視化・ブロック」され、可観測性・回復性・冪等性を備えた本番運用品質の診断ゲートになります。
# .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)を参照してください。
9. 自動診断の限界 — ツールが「原理的に」見つけられないもの
ここまでで、OWASP Top 10:2025 の A02〜A05・A08・A10 の大半は自動で塞げました。しかし——スキャンが全部グリーンでも、それは「安全」ではありません。
自動ツールが原理的に見つけられない脆弱性があります。それは A01(アクセス制御)の中核=認可 と A06/業務ロジック(WSTG 4.5 / 4.10) です。
なぜ機械には判定できないのか
次のコードを見てください。これは構文的に完璧で、SAST・DASTのどちらも警告を出しません。しかし**重大なIDOR(Broken Object Level Authorization)**です。
// ❌ 認可の欠落:ツールは「正しいコード」と判定するが、他人の請求書が見える
// 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を入れれば、他人の請求書が返る
}
// ✅ 所有権を必ず検証する(リソースを"誰が所有するか"はアプリ固有の事業ルール)
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 でも初版以来ずっと1位——「めったに起きない高度な攻撃」ではなく「最も普通に起きる漏洩」です。
| 自動診断で塞げる(水平の穴) | 自動診断では塞げない(垂直のリスク) |
|---|---|
| インジェクション(A05)、ヘッダー/設定(A02) | 認可/IDOR(A01) |
| 既知の脆弱な依存(A03) | テナント分離・データ越境 |
| 秘密情報の混入、暗号設定(A04) | 業務ロジックの悪用(数量・価格・状態遷移) |
| 既知パターンの欠陥 | 権限昇格・設計の妥当性 |
Next.js × Supabase でのIDOR/認可の検出と、RLS(行レベルセキュリティ)による多層防御は、別記事のIDOR・認可欠陥の検出(RLSとの二重化)で具体的に扱っています。
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で大量にコード生成した直後のリリース前(認可・業務ロジックの抜けが入りやすい)
まとめ — 「自分でできる範囲」を最大化し、その先だけを人に頼む
本記事の地図をもう一度たどります。
- 許可の確認が最優先。自分の資産か書面の許可がある対象だけ。能動スキャン(DAST full)は本番に撃たない。
- 公式の三本柱を最新版で固定:何を探すか=Top 10:2025、どう試すか=WSTG v4.2、どこまで満たすか=ASVS 5.0。
- 自動診断は4レイヤー:SCA(A03)→ シークレットスキャン → SAST(A05)→ DAST/ZAP(A01/A05/A02)。速い順にCIへ。
- CI/SARIFでゲート化。静的=PR毎、動的=夜間/リリース前。回帰を恒久的に防ぐ。
- ツールの限界を正直に。認可/IDOR・テナント分離・業務ロジックは機械に判定できない。ここが監査の領域。
脆弱性診断は「専門家に丸投げするもの」ではありません。水平の穴は、無料の公式ツールで開発者自身が一掃できます。 そのうえで、機械に判定できない垂直リスクだけにお金と人の時間を使う——これが、限られた予算で本番品質のセキュリティを担保する、最も合理的な戦い方です。
私自身、経済産業大臣賞を受賞したB2B SaaSや、本番二重課金0件を達成した決済基盤を、一人 × 生成AI で設計・実装してきました。その過程で固めた「自動化で速く・安く守り、設計判断は人間が握る」ワークフローを、本記事のすべてのコードに落とし込んでいます。まずは npx semgrep scan --config=auto の1コマンドから、あなたのアプリを今日診てみてください。