security CI GitHub Actions

緊急パッチが当たらない?Aikido Safe Chainの運用で直面した課題とGitHub Actionsによる解決策

security CI GitHub Actions

こんにちは!ロリポップ・ムームードメイン事業部のtakiです。

近年、npmパッケージを標的としたサプライチェーン攻撃が急増しています。私たちのプロジェクトでもセキュリティ強化の一環として、パッケージの検証ツールである「Aikido Safe Chain」を導入しました。

しかし、導入してすぐに一つの大きな壁にぶつかりました。それは、安全性のための「24時間制限」が、緊急時のデプロイを妨げてしまうという問題です。

本記事では、Aikido Safe Chainを実運用に載せる中で見えてきた「落とし穴」と、GitHub Actionsの機能を駆使してセキュアかつ柔軟な運用を実現した試行錯誤の過程を共有します。

  1. Aikido Safe Chainとは
  2. 直面した課題:緊急パッチが「24時間制限」でブロックされる
    1. 運用上の「落とし穴」
  3. 解決までの試行錯誤
    1. workflow_dispatchを使う
    2. Commit Status APIを使う
    3. ブランチ名で条件分岐する(成功!)
  4. 最終的なバイパス方法
  5. Tips:GitHub APIのレートリミット対策
  6. おわりに

Aikido Safe Chainとは

Aikido Safe Chainは、npm・yarn・pnpmなどのパッケージマネージャーをラップし、パッケージダウンロード時に以下の検証をリアルタイムで行うツールです。

  1. マルウェアチェック: Aikido Intelと照合し、既知の脅威をブロック
  2. 公開日チェック: 24時間以内に公開されたパッケージをブロック(デフォルト設定)

特に後者は、未知の脆弱性や悪意のあるコードが公開直後に混入するリスクを低減するための強力な防衛策です。

直面した課題:緊急パッチが「24時間制限」でブロックされる

Aikido Safe Chainをインストールすると、npm installnpm ciの実行時に自動で検証が走るようになります。 私たちのプロジェクトでは、以下の3つの環境で運用を開始しました。

  • ローカル開発環境
  • CI(テスト実行)
  • GitHub Actionsでのイメージビルド

ここで問題になったのが、脆弱性が発見されたパッケージに対して公開直後の修正パッチを当てたい という緊急ケースです。

運用上の「落とし穴」

npm ci --safe-chain-minimum-package-age-hours=0のように指定すれば24時間制限を無効化できますが、これを手動で運用しようとすると以下の手順が必要になります。

  1. ci.ymlを書き換えてバイパスを有効化するPRを出し、マージする
  2. セキュリティパッチを当てる本命のPRを出し、マージする
  3. ci.ymlを元に戻すPRを出し、マージする

非常に面倒ですよね。さらに問題なのは、セキュアな状態に戻す部分まで手動だということです。2の後、3を忘れてしまう未来が容易に想像できます。そうなるとプロジェクトはセキュアでない状態のまま放置されてしまいます。

必要なのは、自動でセキュアな状態に戻りつつ必要な時だけバイパスできる仕組みです。 以下では、最終的な解決策に至るまでの試行錯誤を説明します。

解決までの試行錯誤

「必要な時だけバイパスし、通常時はセキュアに保つ」という目標に向け、3つのアプローチを試しました。

バイパスが正しく動作しているかは、npm ciのログにSAFE_CHAIN_MINIMUM_PACKAGE_AGE_HOURS: 0と表示されることで確認しました。

workflow_dispatchを使う

workflow_dispatchは、GitHub Actionsを手動で実行できるトリガーです。実行時にパラメータを指定することもできます。 これを使って、24時間制限バイパスモードを選べるようにしました。

workflow_dispatch:
  inputs:
    bypass_safe_chain:
      description: 'Aikido Safe Chainの24時間制限をバイパス(緊急時のみ)'
      type: boolean
      default: false

workflow_dispatchのRun workflowドロップダウン

workflow_dispatchを使えば、PR上では通常のCI(バイパスなし)が動きつつ、緊急時には手動でバイパス付きのワークフローを実行できます。手動実行でも同じワークフローが成功すれば、PRのチェックを通過させられると考え、この方法を選びました。

さっそく実装して試してみましたが、想定通りにはいきませんでした。workflow_dispatchでバイパスを行うこと自体はできましたが、問題はその結果の扱いです。

CIはPRのChecksとして動作しています。workflow_dispatchで手動実行しても、その結果はPRのステータスには反映されませんでした。手動実行とPRのChecksは別物扱いのようなので、workflow_dispatchでバイパスできても意味がありません。

一方、Dockerイメージビルドはmainにマージされた後に実行されるので、PRのChecksとは無関係です。こちらはworkflow_dispatchでのバイパスで対応できそうだということがわかりました。

というわけで、CI側は別の方法を検討する必要があります。

Commit Status APIを使う

次に、手動実行の結果を Commit Status API で特定のコミットに紐付けようと試みました。

Commit Status APIは、外部サービスやCIからコミットのステータス(success/failure/pending/error)を報告できるAPIです。

workflow_dispatchで手動実行してもPRのChecksに反映されなかったのは、コミットへの紐付けがなかったからだと考えました。Commit Status APIを使えば、PRのコミットにステータスを紐付けられます。そうすればPRのChecksに反映されるはず、と考えました。

- name: Report status to PR
  if: github.event_name == 'workflow_dispatch' && always()
  uses: actions/github-script@v7
  with:
    script: |
      const state = '${{ job.status }}' === 'success' ? 'success' : 'failure';
      await github.rest.repos.createCommitStatus({
        owner: context.repo.owner,
        repo: context.repo.repo,
        sha: context.sha,
        state: state,
        context: 'CI / build',
        description: state === 'success' ? 'CI passed (bypass)' : 'CI failed (bypass)'
      });

しかし、こちらもうまくいきませんでした。

Commit Status APIの結果

API経由で報告したステータスは、既存のChecksとは「別の項目」として表示されてしまいました。GitHub側で設定した「必須のステータスチェック」は失敗したままとなり、マージをブロックし続けることになりました。

ブランチ名で条件分岐する(成功!)

GitHub Actionsでは、ブランチ名を条件にして実行するコマンドを変えることができます。 これを利用して、hotfix/で始まるブランチ名の場合は24時間制限を自動バイパスするようにしました。

- name: Install dependencies
  run: npm ci
  env:
    # hotfix/ ブランチでは24時間制限をバイパス(緊急セキュリティパッチ用)
    SAFE_CHAIN_MINIMUM_PACKAGE_AGE_HOURS: ${{ startsWith(github.head_ref, 'hotfix/') && '0' || '' }}

この方法なら、PR上で動くCIそのものがバイパスされたものになります。

開発者はhotfix/ブランチを切るだけでよく、YAMLの書き換えは不要です。また、hotfix/以外のブランチでは常に24時間制限が有効なまま維持されます。

まさに「必要なときだけバイパスし、通常時はセキュアに保つ」という目標を達成できました。

最終的なバイパス方法

今回の対応により、各環境でのバイパス手法が以下のように整理されました。

環境 バイパス方法
CI ブランチ名で条件分岐
イメージビルド workflow_dispatch
ローカル開発環境 環境変数を手動で指定

Tips:GitHub APIのレートリミット対策

運用中、CI/CDでinstall-safe-chain.shを実行する際にGitHub APIのレートリミットエラーが発生することがありました。 これは、スクリプトが「最新バージョン」を確認するためにAPIを叩くことが原因です。

# バージョン指定なし
RUN curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh

対策:以下のようにSAFE_CHAIN_VERSIONを明示的に指定することで、APIコールを回避できます。

# バージョン指定あり
ENV SAFE_CHAIN_VERSION="X.X.X"
RUN curl -fsSL https://github.com/AikidoSec/safe-chain/releases/latest/download/install-safe-chain.sh | sh

※バージョン固定になるため、定期的な更新フローとの併用を推奨します。

おわりに

Aikido Safe Chainの24時間制限は、セキュリティと運用のバランスを考える良い機会でした。同じ問題に直面している方の参考になれば幸いです。

今後は他のサービスにも導入していきたいです!