Dockerイメージの脆弱性診断は、ツールを走らせること自体は数分で終わります。つまずくのはその先です。検出が数百件出たときにどれを直すのか、ビルドを止める閾値をどこに置くのか、止めたビルドを誰がどう解除するのか。ここを決めないまま導入すると、スキャンは通しているのに全件無視されるという形骸化した状態に落ち着きます。この記事では Trivy v0.72.0・Docker Scout・Grype v0.116.1 について、公式CLIリファレンスで確認したフラグと終了コードを突き合わせ、CI組み込みと優先順位付けの判断基準までを整理します。
まとめ
- イメージスキャンが見るのはOSパッケージ・言語ライブラリ・設定ファイル・シークレットの4層で、自社アプリのロジック不備や実行時の権限設定は対象外です。
- 検出時の終了コードはツールごとに違います。Trivy は
--exit-codeで自分で指定、Docker Scout と Grype は固定で 2 を返します。ここを揃えないとゲートが機能しません。 - Trivy の既定スキャナは
vuln,secretで、設定ミス検出(misconfig)は明示指定しないと動きません。 - NVDは2024年初頭から滞留を抱え、2026年4月にCISA KEV優先のリスクベース方式へ移行しました。CVSSスコアだけを閾値にする運用は前提が崩れています。
- ゲートは CRITICAL かつ修正版ありの1条件から始め、例外は必ず期限付きで登録します。最初から全件ブロックにすると解除が常態化します。
Dockerの脆弱性診断で検出できる範囲と届かない領域
この言葉は性質の異なる複数のチェックをまとめて指しています。何を診断していないのかを先に区切らないと、スキャンが通ったことを安全の証明だと誤解する事故が起きます。前提となるコンテナ隔離とDockerのアーキテクチャを押さえておくと、以下の切り分けが読みやすくなります。
OSパッケージ・言語ライブラリ・設定・シークレットという4つのスキャン対象
Trivy の公式CLIリファレンスでは --scanners の値として vuln・misconfig・secret・license が定義され、それぞれ見ている層が異なります。vuln が対象にするのはイメージ内のパッケージで、apk・deb・rpm といったOSパッケージと npm や pip などの言語パッケージの両方を脆弱性データベースと突き合わせます。misconfig は設定ファイルの静的検査で、root実行や不要なポート開放を拾う担当。secret が探すのはイメージレイヤーに焼き込まれた認証情報、license は同梱パッケージのライセンス種別です。
注意したいのは既定値です。--scanners の既定は vuln,secret であり、misconfig は明示指定しない限り実行されません。対象の取り違えも起きやすい箇所です。Dockerfile そのものや Terraform・Helm チャートを検査するのは trivy config の担当で、trivy image --scanners misconfig が見るのはイメージ内に含まれる設定ファイルです。Dockerfileの主要命令と記述上の定石をCIで機械的に検査するなら、別ステップとして trivy config を置いてください。
シークレット検出も、イメージスキャンだけでは取りこぼします。焼き込まれる前のコミット段階で止めるほうが確実で、そこは gitleaksによるシークレット検出のようなリポジトリ側ツールの担当領域です。
イメージスキャンでは検出できない自社コードと実行時の権限設定
イメージスキャンは、既知の脆弱性を持つ既知のパッケージが入っているかを照合する仕組みです。照合先のデータベースに載らないものは原理的に検出できません。自社コードのSQLインジェクション(CWE-89)や認可欠落(CWE-862)はCVE番号が振られない以上映らず、ここはSASTやコードレビュー、動的診断の担当です。実行時の構成も同様で、--privileged での起動、/var/run/docker.sock のマウント、ネットワークポリシーの不備は、イメージの中身を見るだけでは判定できません。
スキャン結果ゼロは「既知の脆弱パッケージが入っていない」という意味であって、安全であるという意味ではありません。この線引きを共有しておかないと、診断レポートが免罪符として使われます。
Trivy・Docker Scout・Grypeのコマンドと終了コードの相違点
導入時に最も事故が多いのが終了コードの扱いです。3ツールとも「脆弱性が見つかったらCIを落とす」機能を持ちますが、返す値と指定方法が揃っていません。他社記事のコマンドをそのまま別ツールに置き換えると、ゲートが無言で無効化されます。
| ツール | 版 | ゲート指定 | 検出時の終了コード | 既定のスキャン対象 |
|---|---|---|---|---|
| Trivy | v0.72.0 | --exit-code に値を渡す |
指定した値(慣例的に1) | パッケージCVE+シークレット |
| Docker Scout | Docker Desktop同梱 | --exit-code(値なし) |
2(固定) | パッケージCVE |
| Grype | v0.116.1 | --fail-on に深刻度 |
2(固定) | パッケージCVE |
Trivy と Grype の版は GitHub Releases で公開されている2026年8月1日時点の最新版です。Docker Scout は単体で版を持たず Docker Desktop の版に追随するため、この列だけ表記が異なります。
Trivy v0.72.0でのイメージスキャンとビルド停止の指定
trivy image --severity HIGH,CRITICAL --ignore-unfixed --exit-code 1 myapp:1.0.0
--exit-code は公式リファレンスで「specify exit code when any security issues are found」と定義され、値を自分で決めます。渡さなければ検出があっても終了コードは0のままです。--ignore-unfixed は「display only fixed vulnerabilities」で、内部的には --ignore-status affected,will_not_fix,fix_deferred,end_of_life のショートハンド。修正版が提供されていない脆弱性を外すため、開発者が今アクションを取れる件数だけが残ります。導入初期はこれを付けないと、対処不能なCVEでビルドが落ち続けます。
Trivy 固有で有用なのが --exit-on-eol(「exit with the specified code when the OS reaches end of service/life」)です。ベースイメージのOSがサポート終了に達したこと自体を失敗条件にできます。個別CVEを追うより、EOLを踏んだ時点でベースイメージ更新を強制するほうが根治的です。
Docker Scoutのcvesコマンドと終了コード2
docker scout cves --only-severity critical,high --only-fixed --exit-code myapp:1.0.0
公式リファレンスの -e, --exit-code は「Return exit code ‘2’ if vulnerabilities are detected」と明記され、値を取らない真偽値フラグです。返るのは2で固定。Trivy から移行して --exit-code 1 と書くと、1 がフラグの値ではなく位置引数として解釈されます。
Docker Scout は Docker Desktop に同梱され、追加インストールなしで使えます。起点はリリースノートに「Docker Desktop now ships with Docker Scout.」と記された Docker Desktop 4.17.0(2023年2月27日)。かつての docker scan(Snykベース)は非推奨化のうえ置き換えられ、scan-cli-plugin もアーカイブ済みです。他ツールに無いフィルタとして --ignore-base(ベースイメージ由来を除外)と --only-base(ベースイメージ由来だけを抽出)があり、悪用実績・悪用予測による絞り込みも --only-cisa-kev や --epss-score でCLI側だけで完結します。
Grype v0.116.1のfail-onによる閾値指定
grype myapp:1.0.0 --fail-on high --only-fixed
公式CLIリファレンスの記述は「set the return code to 2 if a vulnerability is found with a severity >= the given severity, options=[negligible low medium high critical]」です。深刻度そのものを引数に取り、その値以上が1件でもあれば2を返します。Trivy のように「表示する深刻度」と「失敗させる条件」を別フラグに分けていないため、表示と判定が常に一致します。SBOM生成の Syft v1.50.0 と組み合わせる前提の設計です。
Trivy・Grype・Syft はいずれも GitHub API 上のライセンス表記が Apache-2.0 で、商用利用に追加費用はかかりません。Docker Scout は Docker アカウントへのログインが必要で、プランごとの機能差は公式の料金ページで確認してください。
既定スキャナと脆弱性DBの出所が3ツールで異なる実務影響
同じイメージでもツールによって件数が一致しません。主な原因は参照する脆弱性データベースの違いです。Trivy の場合、DBは mirror.gcr.io/aquasec を先に試し、失敗すれば ghcr.io/aquasecurity という順序でpullされます。閉域網のCIランナーからこの2つに到達できないとスキャンが起動しません。--db-repository で社内ミラーを指定できますが、公式ドキュメントは「Setting the repository location flags override the default values which include the official db locations」と注意しており、既定を残したい場合は社内ミラーと公式の両方を並べて指定するよう案内しています。
件数差を「どちらかが間違っている」と判断するのは早計です。ディストリビューションのセキュリティトラッカーが「この修正は該当しない」と宣言した脆弱性を片方が拾い片方が落とす、という差は珍しくありません。複数ツールを並べるなら、件数を比較するのではなく、片方だけが出した項目を確認対象にするのが現実的です。
CI以外の選択肢としては、レジストリ側でのスキャンがあります。Harbor は公式ドキュメントで「Harbor provides static analysis of vulnerabilities in images through the open source project Trivy.」と述べており、既定スキャナは Trivy。かつて定番だった Clair は「Clair has been removed as a default scanner in v2.2」とされ、外部スキャナとして追加すれば使えるものの既定からは外れました。Clair を前提にした古い解説を踏襲しないでください。レジストリ側スキャンはCIを通らずにpushされたイメージも拾うため、CIのゲートとは補完関係にあります。
GitHub Actionsへの組み込みとSARIF連携の書き方
ローカルでコマンドが通ったら、次はCIに載せる番です。ここでは trivy-action v0.36.0(2026年4月22日リリース)を使います。GitLab CI を使う場合の判断材料はGitHubとGitLabのCI/CD比較にまとめました。
ビルド直後にスキャンしてジョブを失敗させるワークフロー
スキャンはレジストリへのpush前に置きます。push後だと、脆弱なイメージがすでに配布可能な状態になってしまいます。
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Scan image with Trivy
uses: aquasecurity/[email protected]
with:
image-ref: 'myapp:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
ignore-unfixed: true
exit-code: '1'
公式READMEの入力一覧では exit-code の型が String、既定値が 0 と定義されています。数値で書いても動作しますが、型定義に合わせて引用符を付けるほうが安全です。severity と ignore-unfixed の組み合わせにより、CRITICALとHIGHのうち修正版が出ているものだけがゲート対象になります。
厳格なゲートを保ったままSecurityタブへ結果を残す条件指定
ここで多くの構成が破綻します。exit-code: '1' でジョブが失敗すると後続のSARIFアップロードが実行されず、Securityタブに何も残りません。結果を残したいがために exit-code を0に戻せば、今度はゲートが消えます。公式READMEの解決策は、アップロード側に実行条件を付けることです。
- name: Upload SARIF to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: 'trivy-results.sarif'
if: always() は前段が失敗しても実行を続ける指定です。これでビルドは赤く落ちたまま、検出内容はSecurityタブに蓄積されます。ゲートの厳格さと可視性はトレードオフではありません。
CVSSスコアだけで優先順位を決められない理由と代替指標
「CVSS 7.0以上をブロック」という運用は長らく標準的でした。この前提は現在崩れています。
NVDのエンリッチメント遅延と2026年4月の優先付け方針転換
CVSSスコアやCPE情報を付与しているのはNISTのNVDです。そのNVDが2026年4月15日に公開した状況説明は、「Starting in early 2024, the NVD developed a significant backlog of unenriched CVEs.」と2024年初頭からの滞留を認めたうえで、「We enriched nearly 42,000 CVEs in 2025 — 45% more than any prior year. But this increased productivity is not enough to keep up with growing submissions.」と述べています。2025年に約42,000件を処理して前年比45%増となってもなお、登録の増加に追いついていません。
対応としてNVDはリスクベースの優先付けへ移行しました。CISAのKnown Exploited Vulnerabilities(KEV)カタログ収載分、連邦政府で利用されるソフトウェア、重要ソフトウェアを優先し、NVD公開日が2026年3月1日より前の滞留分は「Not Scheduled」へ移されています。
これが意味するのは、スコアが付いていないCVEが構造的に存在し続けるということです。CVSSの閾値を唯一のゲート条件にしていると、未採番の脆弱性は素通りします。CVSS v4.0がBase単独(CVSS-B)とThreat・Environmentalを組み合わせた表記(CVSS-BT、CVSS-BE、CVSS-BTE)を区別する命名を導入したのも、Base単独では判断材料として足りないという同じ問題意識からです。指標そのものの読み方はCVEの仕組みとCVSS・CWE・NVDの違いで整理しています。
EPSSとCISA KEVで修正対象を絞り込む判断基準
代替となるのは、実際に悪用されているかどうかを見る指標です。CISA KEVは悪用が確認された脆弱性だけを収載したカタログで、ここに載っているものは理屈抜きで最優先。Docker Scout なら --only-cisa-kev で該当分だけを抽出できます。EPSSは今後30日以内に悪用される確率の予測値で、--epss-score や --epss-percentile による閾値フィルタが用意されています。
運用の順序としては、KEV収載分を即時対応、EPSSの上位パーセンタイルを短期の対応対象、残りをベースイメージ更新の定期サイクルで巻き取る三段構えが扱いやすい形です。CVSSは深刻度の目安として残しますが、単独の合否基準からは降ろします。
ビルド失敗ゲートの設計と形骸化を避ける運用
技術的な設定より難しいのが、落ちたビルドをどう扱うかの取り決めです。
段階導入で閾値を上げていく設定手順
既存プロジェクトへの後付けでは、初回スキャンで対処しきれない件数が出るのが通常です。いきなり HIGH 以上をブロックにすると全ジョブが赤になり、開発が止まります。現実的な段取りはこうです。最初の数週間は --exit-code を付けずレポートのみを出し、件数の分布を把握する。次に --severity CRITICAL と --ignore-unfixed でゲートを有効にします。修正版が出ている CRITICAL に絞れば、対象件数は全件ブロックに比べて大きく減ります。それが安定して回るようになってから HIGH を加えてください。
期限付きの例外管理とVEXによる誤検知の抑制
どうしても今直せない脆弱性は必ず出ます。無視するなら記録を残す必要があります。Trivy の .trivyignore は、公式ドキュメントの例のとおり期限付きの記法を持ちます。
# 代替パッケージへの移行完了まで受容
CVE-2019-14697 exp:2026-09-30
exp: で指定した日付を過ぎると除外が自動的に切れ、再びゲートに引っかかります。期限のない例外リストは放置されて肥大化しますが、この記法なら棚卸しが強制されます。使っていないコンポーネントに紐づくCVEについては、VEXで「影響しない」と宣言する方法もあり、Docker Scout の --only-vex-affected や --vex-location が対応します。ただしVEXの記述には、そのコンポーネントを本当に呼び出していないという根拠が必要で、確認せずに書けば単なる隠蔽になります。
実験ブランチと緊急デプロイ経路でゲートを外す判断基準
すべてのパイプラインにブロッキングを置くべきではありません。実験用ブランチやプロトタイプのリポジトリでは過剰です。理由は単純で、開発者がゲートを迂回する習慣を身につけてしまうから。一度「失敗したらスキップフラグを付ける」運用が定着すると、本番パイプラインでも同じことをします。GitHub Actions なら、スキャンは全ブランチで走らせたうえで exit-code を設定するステップにだけ if: github.ref == 'refs/heads/main' のような条件を付け、ブロッキングを本番ブランチに限定してください。
もう一つ避けたいのが、ホットフィックス用の緊急デプロイ経路にCRITICAL以上のブロッキングを置く構成です。障害対応中に無関係なCVEでデプロイが止まる状況は、セキュリティ上もむしろ危険。緊急経路はスキャン結果を記録しつつ通し、デプロイ後に必ずレビューする運用に倒します。ゲートを置く場所は、本番レジストリへのpushと本番デプロイの2点に絞るのが、形骸化させずに効かせるための落としどころです。
よくある質問
Dockerの脆弱性診断は無料のツールだけで運用できますか?
できます。Trivy と Grype、SBOM生成の Syft はいずれもライセンスが Apache-2.0 で、CIに組み込む範囲では追加費用が発生しません。レジストリ側で受け止めたい場合も、Harbor が既定スキャナとして Trivy を同梱しているため無料の構成が組めます。有償製品との差が出るのは、組織横断のダッシュボード、ポリシーの一括配布、監査レポートの出力といった管理機能です。スキャンエンジンの検出力ではなく、複数チーム・複数リポジトリを束ねる運用機能に費用を払うかどうかで判断してください。
ベースイメージ由来のCVEが大量に出る場合はどう対処しますか?
まず原因の所在を切り分けます。Docker Scout には --ignore-base(ベースイメージ由来を除外)と --only-base(ベースイメージ由来だけを抽出)の両方があるので、同じイメージに順番に当てれば、自分たちの層とベース側のどちらに件数が偏っているかが即座に分かります。ベース側が原因なら、個別CVEを追うのではなくタグの更新か、より小さいイメージへの切り替えが有効です。Trivy の --exit-on-eol でOSのサポート終了自体を失敗条件にしておくと、根本原因が放置されにくくなります。
同じイメージなのにスキャン結果が日によって変わるのはなぜですか?
脆弱性データベースが更新されるためです。イメージの中身が固定でも、新しいCVEが公開されれば検出件数は増えます。Trivy の公式READMEには「Trivy DB is built every 6 hours.」「By default, the update interval specified in the metadata file is 24 hours.」とあり、DBは6時間ごとに再ビルドされる一方、ローカル側の取得は既定24時間間隔です。実行のたびに再取得されるわけではありません。昨日通ったビルドが今日落ちるのは異常ではなく、正しく動いている証拠。監査などで再現性が要る場合は、--cache-dir でDBを保存したうえで --skip-db-update を使うか、結果のSARIFやJSONを成果物として残してください。
SBOMを出力すれば脆弱性診断は不要になりますか?
不要にはなりません。SBOMはイメージに含まれるコンポーネントの一覧であり、それ自体は脆弱性の有無を判定しない成果物です。判定は、各コンポーネントを脆弱性データベースと突き合わせる工程で初めて行われます。Trivy は --format に cyclonedx や spdx-json を指定してSBOMを出力でき、Grype は Syft が生成したSBOMを入力として受け取れます。SBOMの価値は、新しいCVEが公開されたときにイメージを再ビルドせず影響範囲を照会できる点にあります。
外部の脆弱性診断サービスに依頼する必要はありますか?
置き換えの関係にはありません。外部サービスの診断はプラットフォーム診断とWebアプリケーション診断に大別され、後者が対象とするのは認可の設計不備やセッション管理の欠陥など、CVE番号の付かない自社実装の問題です。イメージスキャンでは扱えない実行時の権限設定やネットワークポリシーも、プラットフォーム診断の範囲に入ります。イメージスキャンを自動化して既知CVE対応の工数を下げ、人手による診断は自社実装のリスクへ集中させる分業が合理的です。依頼はCI/CDでのスキャンを定着させて既知CVEを潰しきってからのほうが、同じ費用で得られる指摘の質が上がります。