Engineering
Glossvation Engineering
セキュリティ

CI で脆弱性・ライセンスを自動検出する — OSV / コンテナ Scan / License Scan

ソフトウェアサプライチェーンセキュリティを CI に組み込む実務指針。OSV 脆弱性データベース、コンテナイメージスキャン (Trivy / Grype)、ライセンススキャン、SBOM 生成までを GitHub Actions の例で整理。

Glossvation ·

なぜ CI でセキュリティ検出をするのか

脆弱性や不適切なライセンスを本番デプロイ後に発見するより、CI で止める 方が修正コストが桁違いに安い。これを Shift Left (左に寄せる) と呼びます。

CI に組み込むべき自動チェックは大きく 3 つ:

チェック対象代表ツール
依存ライブラリの脆弱性npm / Maven / PyPI / Go module 等osv-scanner, Dependabot, Renovate
コンテナイメージの脆弱性Base image + インストール済 OS パッケージTrivy, Grype, Snyk
OSS ライセンスの許諾依存ライブラリのライセンス条文syft + 自前チェック, FOSSA, licensee

加えて SBOM (Software Bill of Materials) の生成も標準化されつつあり、監査・コンプライアンス対応の基本要素です。

1. OSV で依存脆弱性を検出

OSV データベースとは

OSV (Open Source Vulnerabilities) は Google が主導する オープンソース脆弱性の統一フォーマット + データベース 。従来の CVE / NVD / 各言語固有 DB (GitHub Advisory DB、PyPI Advisory DB 等) を横断して、同じ JSON スキーマで扱えます。

利点:

  • 言語を横断 (Python / Node.js / Go / Java / Rust / Ruby …)
  • バージョン範囲が正確 (CVE より誤報少)
  • オフライン実行可能 (エアギャップ環境でも使える)
  • CLI ツール osv-scanner が公式提供

GitHub Actions への組み込み

name: Security Scan
on: [push, pull_request]

jobs:
  osv-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: google/osv-scanner-action/actions/osv-scanner-action@v1
        with:
          scan-args: |-
            --recursive
            --skip-git
            ./

Pull Request 時にスキャンし、発見があれば PR に警告をコメント。CVSS スコア閾値で fail させることも可能。

手元で実行

# Homebrew
brew install osv-scanner
osv-scanner --recursive .

# Docker
docker run -v $(pwd):/src ghcr.io/google/osv-scanner -r /src

出力例:

+-------------------------------------+------+-----------+---------+----------+
| OSV URL                             | CVSS | ECOSYSTEM | PACKAGE | VERSION  |
+-------------------------------------+------+-----------+---------+----------+
| https://osv.dev/GHSA-xxxx-yyyy-zzzz | 7.5  | npm       | lodash  | 4.17.20  |
| https://osv.dev/CVE-2024-12345      | 9.8  | PyPI      | django  | 4.2.0    |
+-------------------------------------+------+-----------+---------+----------+

Dependabot / Renovate との棲み分け

  • osv-scanner: スキャンのみ (結果を PR / CI 失敗で通知)
  • Dependabot / Renovate: スキャン + 自動 PR 作成 で依存をバージョンアップ

実務では 両方を併用 するのが標準。osv-scanner で Low/Mid 優先度を見つけ、Dependabot が自動アップデート PR を作る。

2. Trivy でコンテナイメージをスキャン

なぜコンテナイメージスキャンが必要か

Dockerfile で FROM ubuntu:22.04 と書くと、そのベースイメージに含まれる OS パッケージ (openssl / glibc 等) の脆弱性が自動的に自社イメージにも入ります。 依存脆弱性スキャン (osv-scanner) だけでは OS パッケージの脆弱性までは捕捉できないので、コンテナ全体の脆弱性スキャン が別途必要です。

Trivy が事実上の標準ツール。GitHub Advisory DB / NVD / OS ベンダー公式を横断して検査。

GitHub Actions 例

jobs:
  container-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build image
        run: docker build -t my-app:${{ github.sha }} .

      - name: Trivy scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: my-app:${{ github.sha }}
          format: sarif
          output: trivy-results.sarif
          severity: CRITICAL,HIGH
          exit-code: 1  # HIGH 以上が見つかったら CI 失敗

      - name: Upload to GitHub Security tab
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: trivy-results.sarif

SARIF 形式で出力 → GitHub Security タブに統合される。誰が何を無視しているかも監査可能。

誤報との付き合い方

脆弱性 DB の警告は false positive が一定数ある。本番で止まるのを避けるために:

  • 初回スキャンで出た既知の問題 を .trivyignore に理由付きで記録
  • 定期的に棚卸し (半年に 1 回)
  • 重大度 CRITICAL のみ CI 失敗、HIGH 以下は PR コメントのみ

本番運用の追加

  • CI だけでなく レジストリ側でも定期スキャン (ECR / Harbor / Docker Hub は標準機能あり)
  • 既にデプロイ済みイメージも含めて、毎日スキャンして新規発見に対応

3. ライセンススキャン

なぜ必要か

OSS ライブラリにはライセンス (MIT / Apache-2.0 / GPL / AGPL …) が付随します。 GPL / AGPL は使い方によって自社コードまで GPL 化を強制 されるため、企業としては意識的な選択が必要。 また ライセンスの欠落 (Unlicensed) はそもそも商用利用不可。

syft + 自前チェック

syft で SBOM を生成し、ライセンス情報を抽出:

syft my-app:latest -o cyclonedx-json > sbom.json

ここから 「禁止ライセンス」 が入っていないかチェック:

# jq で AGPL / GPL を検索
jq '.components[] | select(.licenses[].license.id | test("AGPL|GPL"))' sbom.json

GitHub Actions 例

jobs:
  license-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install syft
        run: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin

      - name: Generate SBOM
        run: syft dir:. -o cyclonedx-json > sbom.json

      - name: Check forbidden licenses
        run: |
          FORBIDDEN=$(jq -r '.components[]? | select(.licenses[]?.license.id | test("^(AGPL|GPL)")) | .name' sbom.json)
          if [ -n "$FORBIDDEN" ]; then
            echo "::error::Forbidden license detected: $FORBIDDEN"
            exit 1
          fi

商用ツール

  • FOSSA / Snyk / Black Duck — GUI + 法務レポート。大規模組織は導入検討
  • GitHub の licensee — リポジトリに LICENSE ファイルがあるかを検出 (補完的な用途)

4. SBOM の生成と保管

SBOM (Software Bill of Materials) は「ソフトウェアの材料表」。使っている全依存ライブラリ・バージョン・ライセンスを機械可読な形式で列挙したもの。

なぜ必要か

  • 米国大統領令 14028 (2021) により連邦政府納入ソフトには SBOM 必須化が進む
  • 日本でも金融庁・経産省が SBOM 活用ガイドラインを公表 (2023)
  • 自社の内部監査、取引先からの要求に応じるため

代表フォーマット

  • CycloneDX (OWASP、拡張性高)
  • SPDX (Linux Foundation、Linux 標準)

どちらも syft / Trivy で出力可能。

保管方針

  • イメージごとに SBOM を生成 → 同じレジストリにイメージと紐づけて保管
  • Sigstore / Cosign で SBOM に署名すれば、サプライチェーン攻撃を検知可能
cosign attest --predicate sbom.json --type cyclonedx my-app:latest

5. 全体統合例 (GitHub Actions)

name: Security
on:
  push:
    branches: [main]
  pull_request:
  schedule:
    - cron: '0 3 * * *'  # 毎日 3:00 JST+9

jobs:
  deps:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: google/osv-scanner-action/actions/osv-scanner-action@v1

  image:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: docker build -t app:${{ github.sha }} .
      - uses: aquasecurity/trivy-action@master
        with:
          image-ref: app:${{ github.sha }}
          severity: CRITICAL,HIGH
          exit-code: 1

  sbom-license:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: |
          curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
          syft dir:. -o cyclonedx-json > sbom.json
      - name: License policy check
        run: |
          FORBIDDEN=$(jq -r '.components[]? | select(.licenses[]?.license.id | test("^(AGPL|GPL)")) | .name' sbom.json)
          test -z "$FORBIDDEN" || (echo "::error::Forbidden license: $FORBIDDEN"; exit 1)
      - uses: actions/upload-artifact@v4
        with:
          name: sbom
          path: sbom.json

落とし穴

1. CI 時間の増加

Trivy + osv-scanner + syft 全部やると数分 - 10 分追加される。

  • 並列ジョブで実行 (jobs: の parallelism を活かす)
  • 完全スキャンは schedule (nightly) のみ、PR は差分スキャン

2. 誤報疲れ

警告が多すぎると誰も見なくなる。

  • 重大度 CRITICAL のみ CI fail、HIGH 以下は Slack 通知のみ
  • 定期的なトリアージ会 (月 1 回)

3. 「known issue」 の管理

既知だが修正しない脆弱性 (false positive、ベンダー未修正等) は 理由付きで ignore:

# .trivyignore
CVE-2024-12345  # vendor won't fix, mitigated by WAF rule WAF-123

理由がないと半年後に何だったか忘れる。

4. ライセンス判定の限界

  • Dual-license (MIT OR GPL) の扱いはツールにより違う
  • カスタムライセンス (独自文言) は自動判定できない → 法務レビュー
  • 商用製品では FOSSA 等の専門ツール + 法務レビューを併用

5. SBOM の鮮度

SBOM は ビルド時点のスナップショット 。 古い SBOM を保管しても最新の脆弱性情報と比較しないと意味がない。 Dependency-Track 等で継続スキャン運用を。

まとめ

  • OSV (osv-scanner) で依存ライブラリの脆弱性を検出
  • Trivy でコンテナイメージの OS パッケージまで横断スキャン
  • syft で SBOM 生成 + ライセンス許諾チェック
  • GitHub Actions に組み込み、SARIF で Security タブ統合、重大度で CI fail を分ける
  • SBOM の継続管理と棚卸し運用で 「Shift Left」 を現実のものに

関連記事