govulncheckとは|Goの脆弱性を到達可能性で検出する仕組みと使い方
govulncheckは、Goチームが公式に提供する脆弱性スキャナです。go.modの依存関係を機械的に照合するだけの一般的なSCAツールと違い、ソースコードのコールグラフをたどって「脆弱な関数を実際に呼び出しているか」まで判定する点が最大の特徴です。呼んでいない脆弱性は原則報告しないため、対応すべき指摘だけが手元に残ります。この記事では、仕組み・インストール・go installから実行、4種類の出力形式、検出結果の抑制(いわゆるignoreの実態)、GitHub Actionsでの継続スキャンまでを、公式仕様に沿って実務目線で整理します。
まとめ:govulncheckの要点
- 正体:Go公式の脆弱性スキャナ。
golang.org/x/vulnに属し、go install golang.org/x/vuln/cmd/govulncheck@latestで導入する。 - 強み:到達可能性解析(コールグラフ/シンボル単位)で「実際に呼ばれる脆弱性」に絞る。使っていない依存の脆弱性でノイズが増えない。
- 参照先:既定でGo公式脆弱性データベース
https://vuln.go.devに問い合わせる(-dbで差し替え可)。 - 出力:
-formatで text(既定)/json/sarif/openvex を選ぶ。CIのコードスキャン連携はsarif。 - ignore:ネイティブの除外フラグは無い(機能要望はgolang/go #59507で継続議論中)。到達可能性による自動抑制・OpenVEX・JSONフィルタで運用する。
- CI:
golang/govulncheck-action@v1で継続実行。sarif出力をGitHubのコードスキャンに載せられる。
govulncheckの役割と到達可能性解析の仕組み
govulncheckが答えるのは「自分のGoコードは、既知の脆弱性のうちどれに本当にさらされているか」という問いです。依存パッケージに脆弱性があっても、その脆弱な関数を一度も呼んでいなければ、実害の経路は存在しません。govulncheckはビルド情報からコールグラフを構築し、あなたのコードから脆弱な関数(シンボル)まで呼び出しが到達するかを静的解析します。
この設計の実利は、指摘の量ではなく質にあります。バージョン照合だけのスキャナは「依存に含まれる脆弱性」を全部並べるため、使っていないコードパスの警告まで山積みになりがちです。govulncheckは到達不能な脆弱性を既定で表に出さないので、対応判断の前段にある「これは自分に効くのか?」という切り分けをツール側が済ませてくれます。テキスト出力では、脆弱な関数までの呼び出し経路(コールスタック)が併記され、どのコードが原因かをそのまま追えます。
照合先はGoチームが管理する脆弱性データベース vuln.go.dev です。標準ライブラリと公開Goモジュールの脆弱性が GO-YYYY-NNNN 形式で収録され、各エントリは影響を受けるシンボル情報まで持ちます。到達可能性解析が成り立つのは、この「関数単位のメタデータ」があるからです。関連する依存管理の考え方はDependabotとは|dependabot.ymlの設定・料金・Renovateとの違いも合わせて押さえておくと、検出(govulncheck)と更新(Dependabot)の分担が整理できます。
インストールと基本的な実行手順
go installでの導入とバージョン確認
導入はGoツールチェーンから1コマンドです。ビルドが必要なため、対象プロジェクトと同等以上のGoが入っている環境で実行します。
go install golang.org/x/vuln/cmd/govulncheck@latest
# 導入したバージョンの確認
govulncheck -version
@latestは解決時点の最新安定版を取得します。安定版は2023年のv1.0.0以降、v1.1系が継続提供されています(正確な最新タグは公式リリースで確認してください)。-versionはgovulncheck自身のバージョンに加え、参照した脆弱性データベースの最終更新時刻も表示するため、CIログで「いつ時点のDBで判定したか」を残せます。
スキャンの実行と-modeの使い分け
もっとも一般的な使い方は、モジュールのルートで全パッケージを対象に走らせる形です。
# ソース解析(既定)。カレントモジュール配下すべてを対象
govulncheck ./...
# ビルド済みバイナリを解析(ソースが手元に無い成果物の点検に)
govulncheck -mode binary ./path/to/binary
-modeの既定はsourceで、ソースコードからコールグラフを組み立てて到達可能性まで見ます。配布物やコンテナ内の実行ファイルだけを点検したいときは-mode binaryを使います。バイナリ解析はシンボル表を手掛かりにするため、ソース解析ほど細かい呼び出し経路は追えませんが、CI成果物やサードパーティ製バイナリの受け入れ検査に向きます。ほかに、後段のツールへ渡す中間情報を書き出す-mode extractもありますが、通常のスキャンでは使いません。
出力の読み方とスキャン粒度(-scan)
テキスト出力は「あなたのコードに影響する脆弱性」と「影響しない(呼び出し到達しない)脆弱性」を分けて示します。前者にはコールスタックが付くので、まずここだけを対応対象として読みます。呼び出し到達の判定粒度は-scanで調整でき、既定は関数単位のsymbol、粗くする場合はpackageやmoduleを指定します。粒度を粗くすると解析は速くなりますが、到達可能性の絞り込みが弱まり指摘は増える方向に働くため、通常は既定のsymbolのままで問題ありません。
出力形式の使い分け:text・json・sarif・openvex
-formatで4形式を選べます。用途がはっきり分かれるため、目的から逆算して選ぶのが実務的です。
| 形式 | 指定 | 主な用途 | 脆弱性検出時の終了コード |
|---|---|---|---|
| text(既定) | なし | 人が読む・ローカル確認 | 非0(CIを落とせる) |
| json | -format json | スクリプト処理・独自フィルタ | 0 |
| sarif | -format sarif | GitHubコードスキャン連携 | 0 |
| openvex | -format openvex | VEX文書として影響有無を記録 | 0 |
ここで見落としやすいのが終了コードの挙動です。text形式は脆弱性を見つけると非0で終了するため、CIのステップをそのまま失敗させられます。一方でjson/sarif/openvexを指定した場合は、脆弱性の有無にかかわらず終了コード0で成功扱いになります。機械可読な出力は「後段で解析する」前提の設計だからです。したがってsarifやjsonを使うCIでは、別途その結果をアップロード・評価する後続ステップを組まないと、脆弱性があっても素通りしてしまいます。
検出結果の抑制と誤検知への対処
「govulncheck ignore」で検索して最初に知っておくべき事実は、特定の脆弱性IDを名指しで無視するネイティブのフラグは存在しないという点です。除外機能は要望として挙がっていますが(golang/go issue #59507)、標準のgovulncheckに--ignoreの類はありません。そのうえで、実運用では次の順で考えると破綻しません。
- まず到達可能性に任せる:使っていない脆弱性は既定で影響ありに含まれません。「大量に出た」と感じたら、まず本当に到達している指摘だけを見ているかを確認します。多くのケースで、追加の抑制設定は不要です。
- 対応方針を記録するならOpenVEX:
-format openvexでVEX文書を出力し、「調査済み・影響なし(not_affected)」といった判断を機械可読で残します。無視ではなく「評価済みであることの証跡」を残す発想で、監査や再スキャン時の説明責任に耐えます。 - CIで落とす対象を絞るならJSON+フィルタ:
-format jsonの出力をスクリプト(jq等)で処理し、特定のOSV ID(GO-YYYY-NNNN)を集計・除外して合否を自前判定します。json/sarifは終了コードが常に0なので、合否判定は必ず後段で実装します。 - サードパーティのラッパーは最終手段:除外ファイルを読むフォーク(exoscale/govulncheck等)や、出力を後処理する社内スクリプトも実在します。ただし本家の更新から遅れるリスクがあるため、恒久運用の第一選択にはしません。
立場を明確にすると、「一律に無視する」運用はgovulncheckの設計思想と相性が悪いと言えます。到達可能性で既に絞られた指摘をさらにID単位で消すより、OpenVEXで「なぜ対応しないか」を残すほうが、後任にとっても監査にとっても健全です。誤検知だと感じた指摘も、多くは「別経路から実は到達している」ケースなので、消す前にコールスタックを一度たどることを勧めます。
GitHub Actionsでの継続的スキャン
golang/govulncheck-actionの最小構成
プルリクエストやpushのたびに自動でスキャンするなら、公式のgolang/govulncheck-action@v1が最短です。最小構成はほぼ1行で、Goのセットアップとチェックアウトはアクション側が面倒を見ます。
name: govulncheck
on: [push, pull_request]
jobs:
vulncheck:
runs-on: ubuntu-latest
steps:
- id: govulncheck
uses: golang/govulncheck-action@v1
with:
go-version-input: '1.24'
go-package: './...'
主な入力は、Goのバージョン指定(go-version-input または go-version-file に go.mod を渡す)、対象パッケージ(go-package、既定 ./...)、出力形式(output-format:text/json/sarif、既定text)、出力先ファイル(output-file)、実行ディレクトリ(work-dir、既定 .)などです。既定のtext形式なら脆弱性検出時にジョブが失敗するため、そのままマージ前ゲートとして機能します。CI基盤そのものの組み立てはGitHub Actionsでビルド・自動テストを設定する方法|CI/CDワークフローの作り方で全体像を掴んでおくと、このスキャンをどのジョブに差し込むか判断しやすくなります。
SARIF出力によるコードスキャン連携
指摘をSecurityタブのコードスキャンアラートとして一覧化したい場合は、output-format: sarifで結果をファイルに出し、github/codeql-action/upload-sarifでアップロードします。前述のとおりsarifは終了コード0のままなので、ジョブを失敗させたいか(ブロッキング)/アラートとして可視化だけするか(非ブロッキング)を、この連携方式で選び分けられます。
- uses: golang/govulncheck-action@v1
with:
go-version-input: '1.24'
output-format: 'sarif'
output-file: 'govulncheck.sarif'
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'govulncheck.sarif'
Trivy・Dependabotとの役割分担
govulncheckは万能ではありません。守備範囲を正しく分担させると、CIのセキュリティ体制が過不足なく組めます。要点は「govulncheckはGoコードの到達可能性、他ツールは網羅と更新」という切り分けです。
| ツール | 主担当 | 強み | 単独では弱い点 |
|---|---|---|---|
| govulncheck | Goコードの脆弱性検出 | 到達可能性で実害だけに絞る | Go以外・依存の更新自動化は非対象 |
| Trivy | 広範なSCA・コンテナ | OS/多言語/イメージを網羅 | 到達可能性は見ない(呼ばない脆弱性も出る) |
| Dependabot | 依存の更新自動化 | 脆弱な依存へのPRを自動作成 | コードでの到達可否は判定しない |
実務での組み合わせはこうなります。govulncheckで「今このコードに効く脆弱性」を止め、GitHub ActionsとDependabotの連携設定で依存を継続的に上げ、コンテナやGo以外のレイヤーはTrivy(トリビー)とは|使い方・インストール・読み方の入門ガイドで網羅する、という三層です。加えて秘密情報の混入は静的スキャンでは拾えないため、gitleaksとは(シークレット検出)を別レーンで走らせます。
govulncheckだけに頼るべきでない場面もはっきりしています。Go以外の言語やベースイメージのOSパッケージはそもそも対象外です。またリフレクションやcgoを多用するコードでは、静的なコールグラフが呼び出しを追い切れず、到達可能性の判定が保守的(=影響ありに寄る、あるいは経路を示せない)になることがあります。こうした構成では、govulncheckの結果を「Goコード分の一次フィルタ」と位置づけ、網羅系スキャナと併用するのが現実的です。
よくある質問
govulncheckのバージョンはどう確認しますか?
govulncheck -versionで本体のバージョンと、参照した脆弱性データベースの更新時刻が表示されます。CIでは@latestで導入しつつ、このログを残すと再現性を担保できます。
特定の脆弱性をignore(無視)できますか?
IDを名指しで無視する標準フラグはありません(要望はgolang/go #59507で継続中)。到達可能性による自動抑制を基本に、対応しない判断はOpenVEX出力で証跡化するか、json出力をフィルタして自前で合否判定します。
CIで脆弱性があってもジョブを落とさない方法は?
-format jsonまたは-format sarifを使うと終了コードが常に0になります。sarifはコードスキャンへアップロードして可視化のみ、jsonは後段スクリプトで合否を制御、という使い分けができます。逆に確実に落としたいならtext形式のままにします。
go installできない・見つからない場合は?
パスの問題であれば$(go env GOPATH)/binがPATHに含まれているかを確認します。ビルドに失敗する場合は、対象コードと同等以上のGoバージョンで導入し直してください(govulncheckはソースからビルドされます)。
govulncheckと go.dev の脆弱性データベースの関係は?
govulncheckは既定でvuln.go.devを参照します。これはGoチームが管理する公式DBで、収録内容はブラウザからもpkg.go.dev/vulnで確認できます。オフライン環境では-dbでミラーを指定します。