Ritolabo
  1. Home
  2. Github
  3. Dependabot Critical Alert の Slack 通知実装

Dependabot Critical Alert の Slack 通知実装

  • 公開日
  • カテゴリGithub
  • タグGitHub
Dependabot Critical Alert の Slack 通知実装

Dependabot が脆弱性を検出して PR を作成しても、見落とすリスクがある。

特に Critical な脆弱性は環境によっては即座に対応したいため、Slack 通知で見逃さない仕組みを作る。

通知される条件

  • Dependabot が PR を作成したとき
  • Severity が critical(CVSS 9.0以上)のとき

Slack 通知イメージ(blocks 形式でリッチな表示に):

┌──────────────────────────────────────┐
│  CRITICAL Security Alert             │
├──────────────────────────────────────┤
│ Repository:     Severity:            │
│ user/repo       critical (9.8)       │
├──────────────────────────────────────┤
│ PR:                                  │
│ Bump immer from 9.0.5 to 10.0.3     │
├──────────────────────────────────────┤
│ Package:                             │
│ immer                                │
├──────────────────────────────────────┤
│ Links:                               │
│ View Alert | View Advisory(CVE-XXX) │
└──────────────────────────────────────┘

アーキテクチャ

各リポジトリには呼び出し用の薄いワークフローのみを配置し、実処理は shared-workflows リポジトリの Reusable Workflow に集約する構成。GitHub App トークンで Dependabot alerts API から脆弱性情報を取得し、Critical の場合のみ Slack に通知する。

┌─────────────────────────────────────────────────────────────┐
│  各リポジトリ (様々なプロジェクト)                              │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ .github/workflows/dependabot-critical-alert.yml     │   │
│  │ (呼び出しワークフロー)                                │   │
│  └──────────────────────┬──────────────────────────────┘   │
└─────────────────────────┼───────────────────────────────────┘
                          │ uses: YOUR_USERNAME/shared-workflows/...
                          ▼
┌─────────────────────────────────────────────────────────────┐
│  shared-workflows                                            │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ .github/workflows/dependabot-critical-alert.yml     │   │
│  │ (Reusable Workflow)                                 │   │
│  └──────────────────────┬──────────────────────────────┘   │
└─────────────────────────┼───────────────────────────────────┘
                          │
          ┌───────────────┼───────────────┐
          ▼               ▼               ▼
    GitHub App      Dependabot API    Slack Webhook
    Token生成        アラート取得       通知送信

使用する GitHub Actions

できるだけ簡単に実装するため、以下の GitHub Actions を利用。

Action用途
actions/create-github-app-tokenGitHub App トークン生成
dependabot/fetch-metadataPR からパッケージ名等を取得
slackapi/slack-github-actionSlack 通知(公式)

Dependabot alerts API と認証

脆弱性の Severity や CVSS スコアなどの詳細情報を取得するために、Dependabot alerts API を利用する。

Dependabot alerts API へのアクセスには認証が必要だが、GITHUB_TOKEN では Dependabot alerts API にアクセスできないため、選択肢は2つ:

方式メリットデメリット
PAT(Personal Access Token)設定がシンプル有効期限あり、個人に紐づく
GitHub App有効期限なし、権限を細かく制御可能初期設定がやや複雑

今回は GitHub App を採用。

  • トークンの有効期限切れを気にしなくてよい
  • 必要最小限の権限(Dependabot alerts: Read-only)だけを付与できる
  • 個人アカウントに依存しない

事前に必要な設定

GitHub UI 上で、事前に必要な設定を行う。

1. リポジトリの Dependabot 設定を有効化

各リポジトリで以下を有効化する:

  1. GitHub リポジトリの Settings > Code security を開く
  2. 以下の項目を有効化:
    • Dependency graph: 依存関係の可視化
    • Dependabot alerts: 脆弱性アラートの有効化
    • Dependabot security updates: 脆弱性修正 PR の自動作成

これにより、Dependabot が脆弱性を検出した際に自動で PR を作成するようになる。

2. GitHub App の作成

  • Permissions:
    • Repository permissions > Dependabot alerts: Read-only
    • Repository permissions > Metadata: Read-only
  • Repository access: 対象リポジトリを追加

3. Dependabot Secrets の設定(各リポジトリ)

Secret名内容
SLACK_WEBHOOK_URLSlack Incoming Webhook URL
APP_IDGitHub App の App ID
APP_PRIVATE_KEYGitHub App の Private Key

Point: Actions secrets ではなく Dependabot secrets に設定する必要がある(Dependabot が作成する PR では Actions secrets にアクセスできないため)

GitHub Actions 実装

単一リポジトリで動作するものを実装した後、複数リポジトリでも利用できる形にしていく。

フェーズ 1: 単一リポジトリ版

1つのリポジトリだけで使う場合のシンプルな実装。

.github/workflows/dependabot-critical-alert.yml

name: Dependabot Critical Alert

on:
  pull_request:
    types: [opened]

permissions:
  contents: read
  pull-requests: read

jobs:
  notify:
    runs-on: ubuntu-latest
    if: github.actor == 'dependabot[bot]'

    steps:
      - name: Generate GitHub App token
        id: app-token
        uses: actions/create-github-app-token@v1
        with:
          app-id: ${{ secrets.APP_ID }}
          private-key: ${{ secrets.APP_PRIVATE_KEY }}

      - name: Fetch Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Get alert details
        id: alert
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
          PACKAGE: ${{ steps.metadata.outputs.dependency-names }}
        run: |
          ALERT=$(gh api "/repos/${{ github.repository }}/dependabot/alerts" \
            --jq ".[] | select(.state == \"open\") | select(.dependency.package.name == \"${PACKAGE}\")" | head -1)

          if [ -z "$ALERT" ]; then
            echo "found=false" >> $GITHUB_OUTPUT
            exit 0
          fi

          NUMBER=$(echo "$ALERT" | jq -r '.number')
          SEVERITY=$(echo "$ALERT" | jq -r '.security_advisory.severity')
          CVSS=$(echo "$ALERT" | jq -r '.security_advisory.cvss.score // empty')
          GHSA_ID=$(echo "$ALERT" | jq -r '.security_advisory.ghsa_id')
          CVE_ID=$(echo "$ALERT" | jq -r '.security_advisory.cve_id // empty')

          echo "Found alert: number=${NUMBER}, severity=${SEVERITY}, cvss=${CVSS}"

          echo "found=true" >> $GITHUB_OUTPUT
          echo "number=${NUMBER}" >> $GITHUB_OUTPUT
          echo "severity=${SEVERITY}" >> $GITHUB_OUTPUT
          echo "cvss=${CVSS}" >> $GITHUB_OUTPUT
          echo "ghsa_id=${GHSA_ID}" >> $GITHUB_OUTPUT
          echo "url=https://github.com/${{ github.repository }}/security/dependabot/${NUMBER}" >> $GITHUB_OUTPUT

          if [ -n "$CVE_ID" ]; then
            echo "advisory_text=View Advisory(${CVE_ID})" >> $GITHUB_OUTPUT
          else
            echo "advisory_text=View Advisory" >> $GITHUB_OUTPUT
          fi

      - name: Send Slack notification
        if: steps.alert.outputs.found == 'true' && steps.alert.outputs.severity == 'critical'
        uses: slackapi/slack-github-action@v2.1.1
        env:
          REPOSITORY: ${{ github.repository }}
          SEVERITY: "critical (${{ steps.alert.outputs.cvss }})"
          PR_URL: ${{ github.event.pull_request.html_url }}
          PR_TITLE: ${{ github.event.pull_request.title }}
          PACKAGE: ${{ steps.metadata.outputs.dependency-names }}
          ALERT_URL: ${{ steps.alert.outputs.url }}
          ADVISORY_URL: "https://github.com/advisories/${{ steps.alert.outputs.ghsa_id }}"
          ADVISORY_TEXT: ${{ steps.alert.outputs.advisory_text }}
        with:
          webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
          webhook-type: incoming-webhook
          payload: |
            blocks:
              - type: header
                text:
                  type: plain_text
                  text: "CRITICAL Security Alert"
                  emoji: true
              - type: section
                fields:
                  - type: mrkdwn
                    text: "*Repository:*\n${{ env.REPOSITORY }}"
                  - type: mrkdwn
                    text: "*Severity:*\n${{ env.SEVERITY }}"
              - type: section
                text:
                  type: mrkdwn
                  text: "*PR:*\n<${{ env.PR_URL }}|${{ env.PR_TITLE }}>"
              - type: section
                text:
                  type: mrkdwn
                  text: "*Package:*\n${{ env.PACKAGE }}"
              - type: section
                text:
                  type: mrkdwn
                  text: "*Links:*\n<${{ env.ALERT_URL }}|View Alert> | <${{ env.ADVISORY_URL }}|${{ env.ADVISORY_TEXT }}>"

フェーズ 2: 複数リポジトリ共通化版(Reusable Workflows)

複数リポジトリで同じワークフローを使いたい場合は、Reusable Workflows で共通化する。

Reusable Workflows は、GitHub Actions でワークフローを再利用する仕組み。workflow_call トリガーで定義したワークフローを、他のリポジトリから uses: で呼び出せる。

Composite Actions(通常の GitHub Actions)は Private リポジトリ間で共有できないが、Reusable Workflows ならアクセス許可を設定すれば Private でも共有できる。

Reusable Workflows の前提条件

Reusable Workflows を別リポジトリから呼び出すには、以下の条件がある:

環境Private リポジトリPublic リポジトリ
Organization同一 Organization 内で設定すれば利用可能利用可能
個人アカウント同一ユーザー所有で設定すれば利用可能利用可能

個人アカウントの場合: Private リポジトリに配置した Reusable Workflows も設定次第で共有可能だが、設定が必要。試してみる程度なら Public リポジトリ に配置するのが手軽。

個人アカウントで private リポジトリに対する同一ユーザー所有の設定は以下:

shared-workflows の Settings > Actions > General > Access

「Accessible from repositories owned by 'YOUR_USERNAME' user」を選択

◯ Not accessible  
  Workflows in other repositories cannot access this repository.  

● Accessible from repositories owned by the user 'YOUR_USERNAME'  
  Workflows in other repositories that are owned by the user 'YOUR_USERNAME' can access the actions and reusable workflows in this repository. Access is allowed only from private repositories.

参考:Managing GitHub Actions settings for a repository - GitHub Docs

共通ワークフロー(shared-workflows リポジトリに配置)

shared-workflows/.github/workflows/dependabot-critical-alert.yml

name: Dependabot Critical Alert

on:
  workflow_call:
    secrets:
      SLACK_WEBHOOK_URL:
        required: true
      APP_ID:
        required: true
      APP_PRIVATE_KEY:
        required: true

jobs:
  notify:
    runs-on: ubuntu-latest
    if: github.actor == 'dependabot[bot]'

    steps:
      - name: Generate GitHub App token
        id: app-token
        uses: actions/create-github-app-token@v1
        with:
          app-id: ${{ secrets.APP_ID }}
          private-key: ${{ secrets.APP_PRIVATE_KEY }}

      - name: Fetch Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Get alert details
        id: alert
        env:
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
          PACKAGE: ${{ steps.metadata.outputs.dependency-names }}
        run: |
          ALERT=$(gh api "/repos/${{ github.repository }}/dependabot/alerts" \
            --jq ".[] | select(.state == \"open\") | select(.dependency.package.name == \"${PACKAGE}\")" | head -1)

          if [ -z "$ALERT" ]; then
            echo "found=false" >> $GITHUB_OUTPUT
            exit 0
          fi

          NUMBER=$(echo "$ALERT" | jq -r '.number')
          SEVERITY=$(echo "$ALERT" | jq -r '.security_advisory.severity')
          CVSS=$(echo "$ALERT" | jq -r '.security_advisory.cvss.score // empty')
          GHSA_ID=$(echo "$ALERT" | jq -r '.security_advisory.ghsa_id')
          CVE_ID=$(echo "$ALERT" | jq -r '.security_advisory.cve_id // empty')

          echo "found=true" >> $GITHUB_OUTPUT
          echo "number=${NUMBER}" >> $GITHUB_OUTPUT
          echo "severity=${SEVERITY}" >> $GITHUB_OUTPUT
          echo "cvss=${CVSS}" >> $GITHUB_OUTPUT
          echo "ghsa_id=${GHSA_ID}" >> $GITHUB_OUTPUT
          echo "url=https://github.com/${{ github.repository }}/security/dependabot/${NUMBER}" >> $GITHUB_OUTPUT

          if [ -n "$CVE_ID" ]; then
            echo "advisory_text=View Advisory(${CVE_ID})" >> $GITHUB_OUTPUT
          else
            echo "advisory_text=View Advisory" >> $GITHUB_OUTPUT
          fi

      - name: Send Slack notification
        if: steps.alert.outputs.found == 'true' && steps.alert.outputs.severity == 'critical'
        uses: slackapi/slack-github-action@v2.1.1
        env:
          REPOSITORY: ${{ github.repository }}
          SEVERITY: "critical (${{ steps.alert.outputs.cvss }})"
          PR_URL: ${{ github.event.pull_request.html_url }}
          PR_TITLE: ${{ github.event.pull_request.title }}
          PACKAGE: ${{ steps.metadata.outputs.dependency-names }}
          ALERT_URL: ${{ steps.alert.outputs.url }}
          ADVISORY_URL: "https://github.com/advisories/${{ steps.alert.outputs.ghsa_id }}"
          ADVISORY_TEXT: ${{ steps.alert.outputs.advisory_text }}
        with:
          webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
          webhook-type: incoming-webhook
          payload: |
            blocks:
              - type: header
                text:
                  type: plain_text
                  text: "CRITICAL Security Alert"
                  emoji: true
              - type: section
                fields:
                  - type: mrkdwn
                    text: "*Repository:*\n${{ env.REPOSITORY }}"
                  - type: mrkdwn
                    text: "*Severity:*\n${{ env.SEVERITY }}"
              - type: section
                text:
                  type: mrkdwn
                  text: "*PR:*\n<${{ env.PR_URL }}|${{ env.PR_TITLE }}>"
              - type: section
                text:
                  type: mrkdwn
                  text: "*Package:*\n${{ env.PACKAGE }}"
              - type: section
                text:
                  type: mrkdwn
                  text: "*Links:*\n<${{ env.ALERT_URL }}|View Alert> | <${{ env.ADVISORY_URL }}|${{ env.ADVISORY_TEXT }}>"

呼び出し側ワークフロー(各リポジトリに配置)

.github/workflows/dependabot-critical-alert.yml

name: Dependabot Critical Alert

on:
  pull_request:
    types: [opened]

permissions:
  contents: read
  pull-requests: read

jobs:
  notify:
    uses: YOUR_USERNAME/shared-workflows/.github/workflows/dependabot-critical-alert.yml@main
    secrets: inherit

共通化のポイント

項目フェーズ 1フェーズ 2
メンテナンス各リポジトリで個別共通リポジトリで一括
変更の反映各リポジトリで修正共通リポジトリの修正のみ

まとめ

Dependabot の Critical アラートを Slack 通知する仕組みを実装した。

  • GITHUB_TOKEN では Dependabot alerts API にアクセスできないため、GitHub App を作成してトークンを生成することで解決できる。
  • Dependabot が作成する PR では Actions secrets にアクセスできないため、Dependabot secrets に設定する必要がある点も注意が必要。
  • 複数リポジトリで同じ仕組みを使いたい場合は、Reusable Workflows で共通化できる。個人アカウントでも設定次第で Private リポジトリで共有可能なので、Organization でも個人アカウントでも活用できる。

Author

rito

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級