AI事業本部 AIオペレーションテクノロジーカンパニーの金綱雅也(@masaaya)です。
2025年11月、Shai-Hulud 2.0 と呼ばれる npm サプライチェーン攻撃が発生し大量の npm パッケージ・リポジトリが影響を受けました。
今回の事例が示しているのは プロセスに露出しているシークレットは組織規模に関係なく常に狙われ得る という事実です。
本記事ではこの教訓を踏まえ、GitHub 認証において今回の攻撃の主要ターゲットとなった Personal Access Token (PAT) に依存する運用を見直し、パスワードマネージャーと ssh-agent による代理署名を応用した SSH 認証の方法を紹介していきます。
Shai-Hulud 2.0 の概要
Shai-Hulud 2.0 は、npm エコシステムを標準とした観測史上最速レベルのサプライチェーン攻撃です。攻撃のメカニズムはおおまかに次のとおりです。
- 正規の npm パッケージメンテナーの認証情報を不正入手してアカウントを乗っ取る
- 乗っ取ったアカウントを使って人気パッケージをトロイの木馬化する
- 感染パッケージがインストールされると
preinstallスクリプトでマルウェアが自動実行され、exfiltration 用リポジトリを作成して環境内のシークレットを push する - 盗んだ npm トークンを使ってパッケージの不正バージョンを公開する
一部の感染ホストでは盗まれたトークンを用いて self-hosted runner が登録されるなどの侵入行為も報告されています。
本攻撃の詳細については、以下のレポートをご参照ください。
- https://www.wiz.io/blog/shai-hulud-2-0-ongoing-supply-chain-attack
- https://cybelangel.com/blog/the-shai-hulud-malware-attack
- https://www.netskope.com/jp/blog/shai-hulud-2-0-aggressive-automated-one-of-fastest-spreading-npm-supply-chain-attacks-ever-observed
GitHub 運用における PAT の問題点
PAT は発行の手軽さとスコープ設定の柔軟さから幅広く利用されてきましたが、「スコープを細かく設定できる」ことと「安全に運用できる」ことは別の問題とみなせます。
特に現場では常にセキュリティだけが最優先されているわけではないため、開発スピードや一時的な対応を優先した結果次のような状況が生まれがちです。
- とりあえず動かすために必要以上の権限を与えてしまう
- 一時的だからと環境変数に平文のトークンを置いてしまう
- CI の secrets 経由で渡したトークンがコンテナイメージに残留してしまう
これらを 1 つずつ潰していく「終わりのないモグラ叩き」だけで十分な防御力を確保することは難しく、現代のサプライチェーン攻撃に対しては
「プロセスの中でシークレットを直接参照する」という構造そのものが、リスク増大要因になり得る
という一段階高い抽象度で問題を捉え直す必要があり、
- 「シークレットの権限を適切に絞って運用する」だけでは不十分
- 「プロセスに対するシークレット露出を最小限に抑え、必要な時に必要な権限だけを外部から間接的に受け取る」
という設計思想へとシフトしていく必要があります。
GitHub 認証を PAT から SSH へ移行する基本方針
ローカル / Dev Container / CI/CD 環境における GitHub へのアクセスは、大きく次の 2 つに分類できます。
- Git 操作 (clone / fetch / pull / push)
- GitHub API
本記事ではこの Git 操作に対して ssh-agent を用いた SSH 認証を適用する方法として解説のスコープを絞らせていただきます。
ローカル / Dev Container 環境における SSH キー管理
SSH キーの扱いにおいて特に重要なのは次の 2 点です。
- 秘密鍵をディスクにベタ置きしない
- プロセスから生の秘密鍵を読めない形で Git 操作を行う
1 に対してはパスワードマネージャー (Bitwarden、1Password など)、2 に対しては ssh-agent を用いたアプローチが有効です。
ssh-agent は Unix ドメインソケットを介して別プロセスとして署名だけを代行してくれる仕組みです。SSH クライアント側は SSH_AUTH_SOCK で指定されたソケットに署名要求を送信し、実際の秘密鍵は エージェントプロセス内部のメモリ上にのみ保持されます。クライアント側には署名結果だけが返されることから、クライアントプロセスから秘密鍵そのものを直接読み取ることはできないため窃取リスクを大きく下げることができます。
また、パスワードマネージャーに Bitwarden を利用している場合はこれに加えて Bitwarden内のSSH Agent 機能を使う構成が有効です。Bitwarden SSH Agent は復号済みの秘密鍵をエージェントプロセスのメモリ上にのみ保持するため、マルウェアが ~/.ssh をスキャンしても鍵ファイルは見つからず、生の秘密鍵が窃取されるリスクをさらに抑制できます。一度ログインすればローカル環境と Dev Container 双方で同じエージェントを利用できる点も Good Point です。
まとめると、ローカルでの作業フローは次のようになります。
- パスワードマネージャーにログインし、Vault をアンロックする
- GitHub 用の SSH キーペアを生成する
- 公開鍵を GitHub に登録する
- ssh-agent(例:Bitwarden SSH Agent)を有効化し、
SSH_AUTH_SOCKを設定する - 以降の Git 操作はすべてソケット経由で行い、秘密鍵本体には直接触れない
Dev Container での再現
Dev Container 環境でも基本方針は同じで、ホスト側で動作しているソケットだけをコンテナに渡す構成をとります。簡単な設定例は次のとおりです。
// .devcontainer/devcontainer.json
{
"name": "ssh-agent-auth-in-devcontainer",
"dockerComposeFile": "../docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/app",
"remoteUser": "root",
"features": {}
}
# docker-compose.yml
services:
app:
image: mcr.microsoft.com/devcontainers/base:debian
volumes:
- .:/workspaces/app
- ${SSH_AUTH_SOCK}:/ssh-agent:ro # ホストのソケットをマウント
environment:
SSH_AUTH_SOCK: /ssh-agent # コンテナ内でのパスを上書き
working_dir: /workspaces/app
tty: true
stdin_open: true
- ホストで動作する ssh-agent (例:Bitwarden SSH Agent) が署名処理を担当する
- コンテナにはソケットだけが渡される(
ssh-add -lを実行するとホストと同じ鍵が見える) - マウントしているのはソケットのみであり、秘密鍵ファイルそのものではない
- コンテナ内のファイルシステム上には秘密鍵ファイルが存在しないため、コンテナ内から直接読み取ることはできない
CI/CD 環境における ssh-agent と Deploy Key の活用
CI/CD 環境 (ここでは GitHub Actions) においても基本方針は変わりませんが、パスワードマネージャーが利用できないため Deploy Key + ssh-agent の組み合わせが現実的な選択肢になります。
Shai-Hulud 2.0 では盗まれた PAT を用いて self-hosted runner の登録・乗っ取りが行われたケースも確認されており、CI/CD においても秘密鍵を環境に露出させずソケット経由の署名だけで GitHub にアクセスする設計が必要です (この構成は GitHub 公式ドキュメントの推奨に沿っています)。
以下に webfactory/ssh-agent を使った簡単な例を示します。
# actions-ssh-agent.yaml
...
- uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.DEPLOY_KEY }}
- name: docker build
run: |
docker build --ssh default .
...
# Dockerfile
...
RUN mkdir -p -m 0700 /root/.ssh \
&& ssh-keyscan github.com >> /root/.ssh/known_hosts
RUN --mount=type=ssh \
git clone git@github.com:orgname/privaterepo.git
...
- 秘密鍵は Actions の一時的な実行環境と ssh-agent のメモリ上にのみ存在し、コンテナイメージには残らない (
echoなどの出力ミスには要注意) - 個人の SSH キーではなく、そのリポジトリ専用の Deploy Key を払い出す
- Deploy Key の権限は read-only を基本とし、必要な場合にのみ write を付与する
まとめ
本記事では、GitHub における Git 操作を SSH に一本化し PAT に依存しないための認証設計・運用方法の一例を紹介しました。
- Git 操作は SSH (パスワードマネージャー × ssh-agent) で行う
- CI/CD では Deploy Key + ssh-agent を基本構成とする
Shai-Hulud 2.0 を受けて PAT の利用に対して各所でさまざまな制限が検討されているかと思いますが、本記事がその議論の一助となれば幸いです。
