有限会社スプレッドワークス/Spread Works Inc.
ECサイト構築Webサイト構築Webシステム開発マーケティング支援制作実績課題解決
SERVICESサービス紹介
ECサイト構築・運用
CASES / WORKS課題解決 / 制作実績
NEWS / COLUMNお知らせ / ナレッジ・コラム
技術アーカイブス

GitHub Actionsとpnpm auditで実装するNodeモジュールの脆弱性監視の自動化

Satoshi NakanoSatoshi Nakano
GitHub Actionsとpnpm auditで実装するNodeモジュールの脆弱性監視の自動化

はじめに

プロジェクトごとに脆弱性のチェックを手動で行うのは大変です。そこで、依存するNodeモジュールの脆弱性を漏れなく確実に把握し、迅速に対応できるよう監視を自動化する仕組みをGithub Actionsで構築しました。 当社ではNodeモジュールの管理にpnpmを利用していますが、npmのaudit機能でも同様の監視自動化は実現可能です。

仕様

今回の仕組みは以下の仕様にしました。

  • 脆弱性情報の取得 pnpmのaudit機能を利用し、プロジェクトの依存関係に対する脆弱性情報を取得する。
  • 定期実行 定期的にGitHub Actionsでauditを実行し、脆弱性情報を取得する。
  • Issueによる管理 auditの結果から各脆弱性をIssueとして管理し、新たな脆弱性が発生した場合はIssueを作成。
  • メンションによる通知 issueの本文にメンションをつけて通知してメールなどで把握するようにする。
  • Issueの自動クローズ 修正された脆弱性に対応するIssueは自動でクローズする。

Github Actionsのworkflowファイル

name: pnpm Audit Security Check

on:
  schedule:
    - cron: '0 0 * * *'
  workflow_dispatch:

defaults:
  run:
    working-directory: "プロジェクトの設置パス"

jobs:
  audit:
    if: github.ref == 'refs/heads/main' // 対象とするブランチ名
    runs-on: ubuntu-latest
    steps:
      - name: リポジトリをチェックアウト
        uses: actions/checkout@v3

      - name: Node.js 環境をセットアップ
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: pnpm をグローバルインストール
        run: npm install -g pnpm

      - name: 依存関係をインストール
        run: pnpm install

      - name: pnpm audit を実行して JSON 出力を保存
        id: audit
        continue-on-error: true
        run: |
          pnpm audit --json > audit.json || touch audit.json

      - name: audit.json の存在確認
        run: |
          ls -la
          cat audit.json

      - name: audit 結果を解析して Issue を作成/更新および不要な Issue をクローズ
        if: always()
        uses: actions/github-script@v6
        with:
          script: |
            const fs = require('fs');
            let auditData;
            try {
              // ファイルパスをリポジトリルートからの相対パスで指定する
              auditData = JSON.parse(fs.readFileSync('audit.json', 'utf8'));
            } catch (error) {
              core.setFailed("audit.json の読み込みに失敗しました: " + error.message);
              return;
            }
            
            // 脆弱性情報の抽出(advisories または vulnerabilities プロパティ)
            let vulnerabilities = [];
            if (auditData.advisories) {
              vulnerabilities = Object.values(auditData.advisories);
            } else if (auditData.vulnerabilities) {
              vulnerabilities = Object.values(auditData.vulnerabilities);
            }
            
            // 現在の audit 結果に含まれる脆弱性タイトルの Set を作成
            const newVulnTitles = new Set();
            for (const vulnerability of vulnerabilities) {
              // 脆弱性識別子:cves があれば先頭の値、なければ github_advisory_id を利用
              const identifier = (vulnerability.cves && vulnerability.cves.length > 0) ? vulnerability.cves[0] : vulnerability.github_advisory_id;
              const titlePart = vulnerability.title ? vulnerability.title : "No title provided";
              const issueTitle = `${identifier} ${titlePart}`;
              newVulnTitles.add(issueTitle);
            }
            
            // 既存の「vulnerability-alert」ラベル付きのオープンな Issue を取得
            const { data: existingIssues } = await github.rest.issues.listForRepo({
              owner: context.repo.owner,
              repo: context.repo.repo,
              labels: 'vulnerability-alert',
              state: 'open',
              per_page: 100
            });
            const existingTitles = new Set(existingIssues.map(issue => issue.title));
            
            // 新たに報告されている脆弱性について Issue を作成
            for (const vulnerability of vulnerabilities) {
              const identifier = (vulnerability.cves && vulnerability.cves.length > 0) ? vulnerability.cves[0] : vulnerability.github_advisory_id;
              const titlePart = vulnerability.title ? vulnerability.title : "No title provided";
              const issueTitle = `${identifier} ${titlePart}`;
              
              if (!existingTitles.has(issueTitle)) {
                let body = `@チーム名\n\n`; // メンション
                body += `**Package:** ${vulnerability.module_name}\n`;
                body += `**Severity:** ${vulnerability.severity}\n`;
                body += `**Vulnerable Versions:** ${vulnerability.vulnerable_versions}\n`;
                body += `**Patched Versions:** ${vulnerability.patched_versions}\n`;
                body += `**Recommendation:** ${vulnerability.recommendation}\n`;
                if (vulnerability.references) {
                  body += `**More info:** ${vulnerability.references}\n`;
                }
                if (vulnerability.cvss && vulnerability.cvss.score) {
                  body += `**CVSS Score:** ${vulnerability.cvss.score} (${vulnerability.cvss.vectorString})\n`;
                }
                
                await github.rest.issues.create({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  title: issueTitle,
                  body: body,
                  labels: ['vulnerability-alert']
                });
              }
            }

            // 既存の Issue のうち、現状の audit 結果に含まれなくなった Issue をクローズする
            for (const issue of existingIssues) {
              if (!newVulnTitles.has(issue.title)) {
                await github.rest.issues.update({
                  owner: context.repo.owner,
                  repo: context.repo.repo,
                  issue_number: issue.number,
                  state: 'closed'
                });
              }
            }

Issueの例

workflowが実行され、脆弱性がある場合は以下のようなIssueが発行されます。

Issueの例

まとめ

Nodeモジュールの脆弱性監視を自動化するために、pnpm audit と GitHub Actions を活用する方法を紹介しました。 この仕組みにより、脆弱性が検出された際は自動でIssueが作成され、修正が行われたものは不要なIssueがクローズされるため、セキュリティリスクを迅速に把握し、対応することが可能となり、開発現場での負担を軽減し、システムの安全性を向上させることができます。 こういった手法を活用して、プロジェクトのセキュリティ管理を強化してもらえればと思います。

Satoshi Nakano
Satoshi Nakano代表取締役 / ITエンジニア / デザイナー / ITコンサルタント

IT業界歴27年。幼少期からプログラミングに触れ、ReactやNext.jsなどのモダンなフロントエンドフレームワークや、JavaやPHPなどのバックエンド言語に精通。独自フレームワークの開発経験を活かし、サーバーサイドからフロントエンドまで幅広く対応しています。技術を深く理解し、効率的かつ拡張性の高いシステム構築を目指すエンジニアです。

カテゴリー
  • すべて

  • 雑記

  • 技術アーカイブス

Powered by SPREAD Commerce on EC-CUBE.copyright © Spread Works Inc. all rights reserved.