はじめに
少し前から個人開発でですが、Pythonのパッケージ管理ツールは uv を使っています。
uvは開発スピードが早く、頻繁に新機能が追加されます。リリースのたびにリリースノートをチェックしているのですが、v0.9.11(2025年11月21日リリース)のアップデートにて、SBOM(Software Bill of Materials)の出力(CycloneDX形式) をサポートするようになりました。
これを使えば、「uvで管理している依存関係をGitHub Actions上で出力し、脆弱性スキャンツールの Grype に読み込ませる」 というパイプラインが簡単に作れそうだったので、実際に構築してみました。
やったこと
-
uv で
uv.lockから正確な依存関係情報(SBOM)を出力する。 - Grype でそのSBOMを読み込み、脆弱性をチェックする。
- GitHub Actions で定期実行し、危険な脆弱性が見つかったら Discord に通知する。
成果物(YAML)
完成したワークフローファイル(.github/workflows/security-scan.yml)がこちらです。
name: Vulnerability Scan
on:
push:
branches: ["main"] # 対象のブランチを指定
pull_request:
branches: ["main"] # 対象のブランチを指定
schedule:
- cron: '0 0 * * *' # 毎日定刻にスキャンを実行
workflow_dispatch: # 手動実行も可能にしておく
jobs:
sbom-scan:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v6 # verはとりあえず最新を指定しています
# uv のインストール
- name: Install uv
uses: astral-sh/setup-uv@v7 # verはとりあえず最新を指定しています
with:
version: "latest"
# SBOM (CycloneDX) の生成
# --no-dev: 本番に不要な開発用ライブラリは除外
# --locked: uv.lock と pyproject.toml の同期を保証(ズレてたらエラーにする)
- name: Generate SBOM with uv
run: uv export --format cyclonedx1.5 --output-file sbom.json --no-dev --locked
# Grype によるスキャン
- name: Scan SBOM with Grype
uses: anchore/scan-action@v7 # verはとりあえず最新を指定しています
with:
sbom: "sbom.json"
fail-build: true # 脆弱性があったら失敗させる
severity-cutoff: "high" # High以上のみ検知
output-format: "table" # ログに見やすい表を出力
# 失敗時のみ Discord に通知
- name: Notify Discord on Failure
if: failure()
run: |
curl -H "Content-Type: application/json" \
-d '{"username": "GitHub Bot", "content": "⚠️ **GitHub Actionsが失敗しました。**\n内容を確認してください: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"}' \
${{ secrets.DISCORD_WEBHOOK_FOR_GITHUB }}
ポイント解説
1. uv export でSBOMを直接吐き出す
v0.9.11 以降では uv export コマンドで CycloneDX 形式(SBOM)を出力できるようになりました。
uv export --format cyclonedx1.5 --output-file sbom.json --no-dev --locked
-
--format cyclonedx: 標準的なSBOMフォーマットを指定。 -
--no-dev: テストツールなどの開発依存を除外し、本番環境のリスクのみにフォーカス。 -
--locked: これが重要です。uv.lockがpyproject.tomlと同期されていない場合にエラーにしてくれます。「古いロックファイルでスキャンしてしまった」という事故を防げます。
2. スキャン対象は pyproject.toml ではなく uv.lock
uv.lock には「Flask 3.0.1」のような実際にインストールされる正確なバージョンが記録されています。
これにより、Grype は誤検知の少ない正確なスキャンが可能になります。
3. ログを見やすくする
anchore/scan-action はデフォルトだとログ出力が機械的で見づらいため、output-format: "table" を指定しました。これにより、GitHub Actionsのログで以下のように確認できます。
NAME INSTALLED FIXED-IN TYPE VULNERABILITY SEVERITY
requests 2.31.0 2.32.0 python CVE-2024-35195 High
urllib3 1.26.18 1.26.19 python CVE-2024-37891 High
実際のエラーログの様子
まとめ
uv の進化により、パッケージマネージャー単体でプロジェクトのSBOM作成が完結するようになり、比較的簡単にPythonパッケージの脆弱性スキャン環境を作れるようになりました。Syft のようにOS単位でSBOMを作成することは出来ませんが、アプリケーションのプラットフォームも多岐にわたってきているので、これだけで十分というプロジェクトもあるかもしれませんね。どなたかのお役に立てれば幸いです。
