9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AIコーディング時代における、ソフトウェアサプライチェーン攻撃に対する防衛術

9
Last updated at Posted at 2026-03-28

はじめに

AIコーディングツール(Claude Code、Codex 等)の普及により、パッケージのインストールはこれまで以上に日常的な操作になった。AIが提案したコードをそのまま実行し、依存パッケージが自動的にインストールされる場面も多い。

しかし、パッケージのインストールとは第三者のコードを導入する行為であり、そこに悪意あるコードが混入していれば、端末上のクレデンシャル、個人情報、業務データが窃取される。2026年2〜3月に相次いだサプライチェーン攻撃は、この脅威が現実のものであることを示した。

本記事では、この攻撃の実態を踏まえ、開発環境を守るための具体的な対策を整理する。

こんな方におすすめ

  • パッケージのインストール(npm install / pip install 等)を行う全ての人
  • AIコーディングツールを使い始めた人も含む
  • 予防A〜Dは全開発者向け、予防E〜GはCI/CD運用者・パッケージメンテナ向け
  • わからない箇所があれば有識者に相談しながら進めること

スピード優先でまとめたので、変なところがあれば指摘もらえると助かります!

初心者向けのまとめスライドはこちら(一部本文よりも詳しい)

何が起きたか

「パッケージ」「依存関係」ってなに?という方はここをクリック

ソフトウェア開発では、よく使われる機能(HTTP通信、日付処理、暗号化等)を自分でゼロから書く代わりに、他の開発者が公開した「パッケージ」(ライブラリとも呼ばれる)を利用する。パッケージはターミナルでコマンドを実行し、レジストリ(配布サイト)からダウンロードしてインストールする。

言語 パッケージマネージャ インストールコマンド例 レジストリ(配布サイト)
JavaScript / Node.js npm, pnpm, yarn npm install lodash npmjs.com
Python pip, uv pip install requests PyPI(pypi.org)

パッケージには依存関係がある。

あなたのプロジェクト
├── lodash(直接依存: 自分でインストールしたもの)
└── express(直接依存)
    ├── body-parser(間接依存: express が内部で使っているもの)
    └── ...(さらにその先の依存)
  • 直接依存: 自分で明示的にインストールしたパッケージ
  • 間接依存: 直接依存のパッケージが内部で使っているパッケージ。npm install express を実行すると、express が依存しているパッケージも自動的にインストールされる

パッケージのインストール時には、postinstall スクリプト(インストール完了後に自動実行されるスクリプト)が実行される場合がある。正規の用途ではネイティブモジュールのビルド等に使われる。間接依存のパッケージの postinstall スクリプトも同様に実行されるため、自分が直接インストールしていないパッケージのコードが動くことがある。


2026年2〜3月、セキュリティスキャナ Trivy のGitHub Actions(CI/CD自動化の仕組み)が侵害され、そこから連鎖的にLLMプロキシ LiteLLM、通信プラットフォーム Telnyx のPyPI(Pythonのパッケージ配布サイト)パッケージにも悪意あるコードが混入した。脅威アクターTeamPCPによる一連の連鎖攻撃。

事象 侵害経路 出典
Trivy再侵害(3/19) 窃取済みPAT(個人アクセストークン)でGitHub Actionsにクレデンシャル窃取コードを注入 2026年3月19日の Trivy 再侵害の概要と対応指針
LiteLLM侵害(3/24) PyPIメンテナアカウント乗っ取り。悪性バージョンを直接アップロード 2026年3月24日の LiteLLM 侵害の概要と対応指針
Telnyx侵害(3/27) PyPIメンテナアカウント乗っ取り。C2サーバーからペイロードを取得する方式に進化 2026年3月27日の Telnyx 侵害の概要と対応指針

加えて、2026年3月31日にはnpmの主要パッケージ axios(週間約1億ダウンロード)もメンテナアカウントの乗っ取りにより侵害された。TeamPCPとの直接的な関連は確認されていないが、手口は共通している。

事象 侵害経路 出典
axios侵害(3/31) メンテナアカウント乗っ取り。悪性依存パッケージ経由で端末を遠隔操作するマルウェアを設置 2026年3月31日の axios 侵害の概要と対応指針

axios では悪性バージョン(1.14.1、0.30.4)に依存パッケージ plain-crypto-js が追加され、postinstall フックでプラットフォーム別のマルウェア(端末を遠隔操作するプログラム。RAT: Remote Access Trojan)が設置された。露出期間は約2〜3時間だったが、間接依存経由でも postinstall フックは発火するため、axios を直接使っていないプロジェクトでも影響を受ける可能性がある。

OS マルウェアの配置先
macOS /Library/Caches/com.apple.act.mond
Windows %PROGRAMDATA%\wt.exe
Linux /tmp/ld.py

この侵害は OpenAI にも波及した。OpenAI の GitHub Actions ワークフローが悪性の axios 1.14.1 をダウンロード・実行し、macOS アプリ(ChatGPT Desktop、Codex 等)の署名用証明書が露出した。OpenAI は証明書のローテーションを実施し、全 macOS ユーザーにアプリの更新を要請する事態となった。

OpenAI 自身が根本原因として「floating tag(commit SHA 固定でない Action 参照)」と「minimumReleaseAge 未設定」を挙げており、本記事の予防E-2(commit SHAへのpin留め)とC-3(検疫期間)の有効性を裏付ける事例となっている(出典)。

なぜ重要か: 誰もが被害者になりうる

パッケージのインストールとは第三者のコードを導入する行為であり、悪意あるコードが混入していれば、そのユーザーの権限で端末上のあらゆる操作を行える。CI/CDの設定ミスが起点ではあるが、最終的な被害は「悪意あるパッケージをインストールした全員」に及ぶ。

LiteLLMの侵害バージョンは pip install litellm するだけで発火し、ローカルマシン上のクレデンシャルを片端から収集・送信した。Telnyxではさらに手口が進化し、メインのペイロードがC2サーバーから取得したバイナリに隠匿されているため、パッケージの静的解析だけでは影響範囲を特定しにくくなっている。

axios侵害ではnpmエコシステムが標的となり、postinstallフック経由で端末を遠隔操作するマルウェアが設置された。クレデンシャルの窃取にとどまらず、端末全体が遠隔操作可能な状態に置かれる。次にどのパッケージが狙われるかは予測できない。npm、PyPI、どのエコシステムでも起こりうる。

理論上は端末上の全ファイルが窃取対象になりうる。クレデンシャルだけでなく、個人情報、業務文書、顧客データなども例外ではない。つまり、端末上に存在するものが少ないほど被害は小さくなる。不要なファイルや過去のプロジェクトの残骸を放置しない、業務用途と個人用途で端末を分ける、本番環境のクレデンシャルや機微データを開発端末に置かないといった基本的な管理が、個別の対策以前の前提になる。

さらに、開発環境自体をコンテナやクラウドに隔離すれば、ホストマシン上のファイルにそもそもアクセスできない状態を作れる(詳細は本文書後半の「開発環境の隔離による封じ込め」を参照)。

窃取対象になったもの

LiteLLM/Trivy侵害のペイロードが実際に収集を試みた対象の一覧。「自分のマシンにこれらが平文で置いてあるか?」というチェックリストとして使える。Telnyx侵害ではC2サーバーからバイナリを取得して実行する方式のため、窃取対象の特定が困難(ワーストケースは端末内全データの漏洩を想定すべき)。

カテゴリ 収集対象のパス・コマンド 何が盗まれるか
SSH鍵 ~/.ssh/id_rsa, id_ed25519, id_ecdsa, authorized_keys, config サーバーへの不正アクセス
Git認証 ~/.git-credentials, ~/.gitconfig リポジトリの読み書き、なりすまし
AWS ~/.aws/credentials, ~/.aws/config, IMDSv2, Secrets Manager, SSM AWSリソースへのフルアクセス
Google Cloud ~/.config/gcloud/ 全体, application_default_credentials.json Google Cloudリソースへのフルアクセス
Azure ~/.azure/ 配下全体 Azureリソースへのフルアクセス
Kubernetes ~/.kube/config, ServiceAccountトークン, kubectl get secrets --all-namespaces クラスタの完全掌握
Docker ~/.docker/config.json レジストリへのpush権限
.envファイル CWDから6階層まで再帰的に .env* を探索 DB接続文字列、APIキーなど
npm ~/.npmrc パッケージ公開権限
DB ~/.pgpass, ~/.my.cnf, Redis設定 データベースへの直接アクセス
TLS証明書 /etc/ssl/private/*.key, *.pem, *.p12 HTTPS通信の復号・なりすまし
暗号通貨 Bitcoin / Ethereum / Solana等のウォレット・鍵ファイル 資産の窃取
シェル履歴 ~/.bash_history, ~/.zsh_history 過去に入力したパスワード・トークン
CI/CD設定 terraform.tfvars, terraform.tfstate, Jenkinsfile インフラのシークレット
VPN /etc/wireguard/*.conf VPN接続情報
システム /etc/passwd, /etc/shadow, 認証ログ アカウント情報

加えて、コマンド実行(printenv, env | grep AWS_ 等)やHTTPアクセス(AWS IMDSv2, K8s API)による収集も行われていた。

その他、技術的な詳細については、

も参考のこと。

また、本記事でも推奨しているTakumi Guardの提供元であるFlatt Securityのセミナー資料も大変参考になる。

どこで守るか: 開発フロー上の予防ポイント

個別の対策に入る前に、ローカル開発とCI/CDそれぞれの開発フローのどこに予防ポイントがあるかを示す。

ローカル開発フロー

予防ポイント 何を防ぐか 対象者 まず何をするか
A. クレデンシャルのリスクを最小化する 窃取されても被害を最小限にする 全開発者 有識者に相談し、発行済みのクレデンシャルと権限を棚卸しする
B. クレデンシャルを平文で置かない 侵害パッケージが実行されても盗めるものがない状態にする 全開発者 B-6(MFA/SSO)は今すぐやる。B-1〜B-5は有識者に相談して対応
C. 悪意あるパッケージを入れない そもそも侵害パッケージをインストールしない 全開発者 C-1(パッケージ選定)は今日からできる。C-2(レジストリプロキシ)は設定1行
D. シークレットをコミットしない クレデンシャルがリポジトリに混入するのを防ぐ 全開発者 gitleaksの導入を有識者と進める

CI/CDフロー

予防ポイント 何を防ぐか 対象者
E. ワークフローの安全性を確保 Actionの改竄やインジェクションによる侵害を防ぐ CI/CD運用者
F. ランナー上のシークレットを守る 侵害が起きても窃取される情報を最小化し、事後追跡を可能にする CI/CD運用者
G. パッケージ公開を安全にする 自分たちが公開するパッケージの侵害を防ぐ パッケージメンテナ

各予防ポイントの具体的な対策

補足: クレデンシャル以外のデータ残留リスク

予防A〜Dではクレデンシャルの保護を扱うが、窃取対象はクレデンシャルだけではない。開発作業を通じて、意識されないまま端末に残る機密データも攻撃者にとっては価値がある。

例えば、Claude Code 等のAIコーディングツールからDWH(BigQuery、Redshift 等)のクエリコマンドをローカルで実行した場合、クエリ結果や実行ログがローカルに残る。これらに個人情報や機密情報が含まれていると、サプライチェーン攻撃時の窃取対象になりうる。

カテゴリ 具体例 リスク
DWHクエリ結果 BigQuery / Redshift のクエリ結果ファイル、シェル履歴に残るクエリ出力 個人情報・顧客データの漏洩
AIツールの会話履歴 Claude Code の場合、会話履歴が ~/.claude/projects/ 配下にプロジェクトごとの JSONL ファイルとして保存される。ツール実行結果(コマンド出力、ファイル内容等)も含まれる 機密コード・業務データの漏洩
一時ファイル /tmp 配下のダンプファイル、CSVエクスポート 業務データの漏洩

対策:

  • 本番データを含むクエリはローカルではなく、クラウド上の隔離環境(Codespaces、Cloud Shell 等)で実行する
  • クエリ結果をローカルに保存した場合は、作業完了後に削除する
  • Claude Code の会話履歴(~/.claude/projects/ 配下)にも機密データが残るため、本番データを扱ったセッション後はファイルの削除を検討する
  • シェル履歴に機密情報を残さない(~/.bash_history / ~/.zsh_history は窃取対象の一覧にも含まれている)

シェル履歴の制御方法:

# 予防: コマンドの先頭にスペースを入れると履歴に記録されない
# (HISTCONTROL に ignorespace または ignoreboth が設定されている場合)
 export SECRET_KEY=abc123  # ← 先頭にスペース

# 設定確認・有効化(~/.bashrc or ~/.zshrc に追記)
export HISTCONTROL=ignoreboth    # bash: スペース先頭 + 重複を除外
setopt HIST_IGNORE_SPACE          # zsh: スペース先頭を除外
# 事後: 履歴から特定の行を削除
# bash
history -d <行番号>               # 特定行を削除
history -w                        # 削除後にファイルに書き出し

# zsh
fc -W                             # 現在の履歴をファイルに書き出し
# ~/.zsh_history を直接編集して該当行を削除
fc -R                             # 編集後に再読み込み

予防A: クレデンシャルのリスクを最小化する(全開発者向け)

個別の保管対策よりも前に、クレデンシャルの発行・権限・運用を見直す。短命トークンでも権限が広ければ危険であり、複数の対策を組み合わせて「窃取されても被害が最小限になる状態」を作る。

基本的な考え方

認証方式の種類:

  • 静的キー(アクセスキー、サービスアカウントキー等): 文字列をファイルや環境変数に保存して使う。有効期限はサービスや設定により異なるが、長期間有効なものもある。盗まれたらそのまま使える
  • OAuth / SSO: ブラウザでログインすると、アクセストークン(短命、1時間程度)とリフレッシュトークン(長命)が発行される。アクセストークンが期限切れになると、リフレッシュトークンを使って自動的に新しいアクセストークンが取得される。ユーザーが再ログインを求められるのは、リフレッシュトークン自体が期限切れまたは無効化されたとき
  • OIDC / Workload Identity: 実行環境(GitHub Actions、Cloud Run 等)が「自分は何者か」をIDプロバイダに証明し、一時的なトークンを受け取る。キーやパスワードをどこにも保存しない。CI/CD 環境で主に使われる

対策の優先順位(上から順に検討する):

優先度 方式 仕組み 窃取された場合
1 権限を制限する ロール/SA借用、スコープ限定トークン(Fine-grained PAT 等)で必要最小限の権限のみ付与 被害範囲が付与された権限内に限定される
2 クレデンシャルを発行しない(OIDC / Workload Identity) 実行環境が身元を証明し、キーなしで認証する。CI/CD環境向け 窃取対象のクレデンシャルが存在しない
3 短命トークン + ログアウト(SSO / OAuth) ブラウザ認証で短命トークンを取得し、使い終わったらログアウトしてリフレッシュトークンを無効化 ログイン中は窃取リスクあり。ログアウト後は無効化される
4 長期クレデンシャル + ローテーション 静的キーを発行し、定期的に更新。最終手段 次回ローテーションまでフルアクセスを許す

具体例:

  • CI/CD → クラウド: OIDC / Workload Identity でキーを発行しない(優先順位2)
  • ローカル → クラウド: ロール/SA借用で権限を絞り、OAuth でログイン、使い終わったらログアウト(優先順位1+3の組合せ)

A-1. 権限を制限する

権限を制限する方法は大きく2つある。いずれも事前の設定が必要。

  • IAM で権限を絞ったロール/SA を用意し、借用して操作する(事前にロール/SA と IAM ポリシーの設定が必要)
# AWS: IAM ロール経由で操作(aws-vault の設定方法は B-1 を参照)
aws-vault exec dev -- aws s3 ls

# Google Cloud: サービスアカウントの権限を借用してログイン
gcloud auth application-default login \
  --impersonate-service-account=SA_NAME@PROJECT_ID.iam.gserviceaccount.com
  • トークンやキーの発行時にスコープを限定する(GitHub Fine-grained PAT の権限選択、OAuth のスコープ指定等)

A-2. 使い終わったらログアウトする

OAuth 認証では、認証ファイルにリフレッシュトークン(長命)が保存される。窃取されるとアクセストークンを再発行し続けられるため、使い終わったらログアウト/revoke で無効化する。

現在のログイン状態を確認:

# AWS
aws configure list                        # 設定されているプロファイル情報
aws sts get-caller-identity               # 現在の認証情報で誰としてアクセスしているか

# Google Cloud(認証が2系統ある)
gcloud auth list                          # gcloud コマンド用の認証アカウント一覧
# SDK/Terraform 用の ADC が存在するか(パスが OS で異なる)
ls ~/.config/gcloud/application_default_credentials.json                  # macOS / Linux
Test-Path "$env:APPDATA\gcloud\application_default_credentials.json"     # Windows (PowerShell)

# Azure
az account list                           # 設定されているサブスクリプション一覧
az account show                           # 現在アクティブなアカウント

ログアウト:

# AWS
aws sso logout                           # SSO の場合
aws-vault clear personal                 # aws-vault の場合(キャッシュされたセッションを削除)
# 静的キー(~/.aws/credentials)の場合はログアウトの仕組みがない → aws-vault への移行を推奨(B-1 参照)

# Google Cloud(使っている方を revoke する)
gcloud auth application-default revoke   # SDK/Terraform 等のアプリケーション用
gcloud auth revoke                       # gcloud コマンド用

# Azure
az logout

A-3. 組織向け: トークンの利用元を制限する

組織アカウントでは、窃取されたトークンを別の端末から使えないようにアクセスポリシーを設定できる。

  • AWS: IAM ポリシーの aws:SourceIp 条件で IP アドレスを制限
  • Google Cloud: Context-Aware Access(BeyondCorp)で管理下デバイスからのみアクセスを許可
  • Azure: Conditional Access で端末のコンプライアンス状態やアクセス元を検証

いずれも Enterprise ライセンスや固定 IP が前提のため、個人アカウントでは利用が難しい。

A-4. ローテーションの原則

長期クレデンシャル(優先順位4)を使わざるを得ない場合:

  • 自動ローテーションが可能なものは自動化する(AWS Secrets Manager の自動ローテーション等)
  • ローテーション時は旧クレデンシャルの即時無効化を含む(Trivy再侵害では、ローテーション完了前に攻撃者が新しいトークンを取得した)
  • 検証・テスト用に一時的に発行したクレデンシャルは、検証完了後に速やかに削除・無効化する
  • ローテーション対象の棚卸し: どのクレデンシャルがどこに保管されているか、一覧化しておく
  • 監査ログ(AWS CloudTrail、Google Cloud Audit Logs 等)を有効にし、不審なクレデンシャル発行操作を検知できるようにする

これらの対策を組み合わせても、認証セッションが有効な間(ログイン中)はトークンがキャッシュ上に存在し、窃取のリスクは残る。根本的にローカルから認証情報を排除するには、開発環境の隔離(本文書後半を参照)を検討する。

予防B: クレデンシャルを平文で置かない(全開発者向け)

侵害パッケージが実行されたとき、ファイルシステム上に平文で存在するクレデンシャルは全て窃取対象になる。原則は「ファイルとして平文で存在するものはコピーできる」。平文ファイルをなくすか、短命にして窃取されても使える時間を限定する。

B-1. クラウドクレデンシャル: 静的キーを排除して短命トークンにする

今どうなっているか確認:

# macOS / Linux
cat ~/.aws/credentials | grep aws_access_key_id
ls ~/.config/gcloud/application_default_credentials.json
ls ~/.azure/servicePrincipal*.json
grep -c "token:" ~/.kube/config
# Windows (PowerShell)
Select-String -Path "$env:USERPROFILE\.aws\credentials" -Pattern "aws_access_key_id" -ErrorAction SilentlyContinue
Test-Path "$env:APPDATA\gcloud\application_default_credentials.json"
Test-Path "$env:USERPROFILE\.azure\servicePrincipal*.json"
Select-String -Path "$env:USERPROFILE\.kube\config" -Pattern "token:" -ErrorAction SilentlyContinue

どうなっていたら危険で、どう直すか:

サービス 危険な状態 安全な状態にする方法
AWS(組織) ~/.aws/credentials にアクセスキーがある aws configure sso でSSO設定 → aws sso login で都度認証(最大12時間)。IAM Identity Center が組織で有効化されている必要がある
AWS(個人) ~/.aws/credentials にアクセスキーがある aws-vault でキーを OS キーチェーンに移し、STS 経由で短命トークンを都度発行する方式に切替(後述)
Google Cloud application_default_credentials.json にサービスアカウントキーがある gcloud auth application-default login でOAuth認証に切替(1時間)。組織でSAキーダウンロードを禁止するポリシーも設定
Azure サービスプリンシパルのキーファイルがある az login でブラウザ認証に切替。可能ならManaged Identityを利用
Kubernetes ~/.kube/config に有効期限のない静的トークンがある aws eks update-kubeconfiggcloud container clusters get-credentials で短命トークンを都度取得する方式に切替

個人AWSアカウントでの aws-vault 導入手順:

組織の IAM Identity Center が使えない個人アカウントでは、aws-vault でアクセスキーを OS のキーチェーン(macOS Keychain / Windows Credential Manager)に保管し、実行時に STS 経由で短命トークンを発行する方式に切り替える。~/.aws/credentials に平文でキーを置く必要がなくなる。

# インストール
brew install aws-vault        # macOS
winget install aws-vault       # Windows
# 1. アクセスキーを aws-vault に登録(OS キーチェーンに保管される)
aws-vault add personal

# 2. 動作確認
aws-vault exec personal -- aws sts get-caller-identity

# 3. 動いたら ~/.aws/credentials からアクセスキーを削除
#    ファイルが空になるならファイルごと削除してよい

以降、AWS コマンドの実行は aws-vault exec personal -- aws <コマンド> で行う。頻繁に使う場合は aws-vault exec personal でサブシェルを開き、その中で通常通り aws コマンドを実行できる。

MFA と組み合わせると、キーチェーンから窃取されても MFA なしでは使えなくなる:

# ~/.aws/config
[profile personal]
mfa_serial = arn:aws:iam::123456789012:mfa/your-user

B-2. SSH鍵: ファイルシステムから秘密鍵をなくす

今どうなっているか確認:

# macOS / Linux
ls -la ~/.ssh/id_*
# Windows (PowerShell)
Get-ChildItem "$env:USERPROFILE\.ssh\id_*" -ErrorAction SilentlyContinue

id_rsa, id_ed25519 等のファイルが存在していたら、それはコピー可能な状態。

どう直すか:

方法 ツール 仕組み
FIDO2 resident key YubiKey等のセキュリティキー ssh-keygen -t ed25519-sk -O resident で生成。秘密鍵はキー内に保持され、ファイルに存在しない。物理タッチが必要なのでマルウェアが単独で使えない
パスワードマネージャ連携 1Password SSH Agent / Secretive SSH Agentプロトコル経由で署名のみ提供。~/.ssh/id_* ファイル自体が不要になる。1Password の場合はデスクトップアプリが必要(CLI のみでは不可)

B-3. .env ファイル: シークレットマネージャから実行時に取得する

今どうなっているか確認:

# macOS / Linux
find . -name ".env*" -not -path "*node_modules*" -not -path "*.git*"
find ~ -maxdepth 3 -name ".env*"
# Windows (PowerShell)
Get-ChildItem -Recurse -Filter ".env*" -Exclude "node_modules",".git" -ErrorAction SilentlyContinue
Get-ChildItem "$env:USERPROFILE" -Recurse -Depth 3 -Filter ".env*" -ErrorAction SilentlyContinue

.env ファイルにシークレットが直書きされていたら危険。平文のシークレットをファイルに置かない方式に切り替える。主な方式は以下の通り。プロジェクトの技術スタックやチーム構成に合わせて選択する。

方式 仕組み .env ファイル 向いているケース
direnv + シークレットマネージャ .envrc から op read 等で都度取得 不要にできる シェル操作に慣れているチーム
dotenvx + 1Password .env を公開鍵暗号で暗号化し、秘密鍵は1Passwordで管理 暗号化して保持 .env をリポジトリ管理したいチーム
1Password CLI単体(op run .env を持たず、vault から環境変数を直接注入 不要にできる 1Passwordを全員が使っているチーム
sops(暗号化ファイル) AWS KMS / Google Cloud KMS / age 等で .env を暗号化 暗号化して保持 クラウドKMSを既に運用しているチーム

なお、環境変数に注入されたシークレットも同一プロセスからは process.env 等で読み取れるため、完全な防御ではない。目的はファイルとして常駐させないことにある。

以下、各方式の導入手順。

direnvの導入と使い方(macOS / Linux):

# 1. direnvのインストール
brew install direnv    # macOS
sudo apt install direnv  # Ubuntu

# 2. シェルにフックを追加(~/.zshrc or ~/.bashrc)
eval "$(direnv hook zsh)"    # zshの場合
eval "$(direnv hook bash)"   # bashの場合

# 3. プロジェクトルートに .envrc を作成
cat > .envrc << 'ENVRC'
# 1Passwordから取得する例
export DATABASE_URL=$(op read "op://dev/db/url")
export API_KEY=$(op read "op://dev/api/key")

# AWS Secrets Managerから取得する例
export SECRET=$(aws secretsmanager get-secret-value --secret-id my-secret --query SecretString --output text)
ENVRC

# 4. direnvに許可を与える(.envrcを変更するたびに必要)
direnv allow

# 5. .envrcを.gitignoreに追加(念のため)
echo ".envrc" >> .gitignore

この方法なら、ディレクトリに cd すると自動的に環境変数がセットされ、離れると解除される。.env ファイルに平文シークレットを書く必要がなくなる。

Windowsでの利用:

winget install direnv でインストールできる。macOS / Linux と同じ .envrc ファイルが使える。

別のアプローチ: dotenvx による .env ファイル自体の暗号化

direnv は「.env を持たない」アプローチだが、dotenvx は「.env を暗号化したまま保持する」アプローチ。悪意あるパッケージが .env を読み取っても暗号文しか見えない。

# 1. dotenvxのインストール
npm install @dotenvx/dotenvx --save-dev

# 2. .envを暗号化(公開鍵・秘密鍵ペアが生成される)
npx dotenvx encrypt

# 3. 秘密鍵(.env.keys内のDOTENV_PRIVATE_KEY)を1Passwordに保管し、.env.keysを削除
# 4. 1Password参照ファイル(.env.op)を作成
echo 'DOTENV_PRIVATE_KEY=op://{vault}/{item}/{field}' > .env.op

# 5. 実行時に復号して環境変数を注入
op run --env-file=.env.op -- npx dotenvx run -- node app.js

暗号化後の .env はリポジトリにコミットできるため、.env.example が不要になり、新しい環境変数の追加もコミットでチーム全員に伝搬する。暗号化は公開鍵のみで行えるため、秘密鍵がなくても変数の追加は可能(npx dotenvx set NEW_KEY "value")。

出典: https://dotenvx.com/docs/quickstart/encryption

1Password CLI単体の例:

# 1. 1Passwordのvaultに環境変数を登録しておく

# 2. テンプレートファイル(.env.template)を作成し、1Password参照を記述
cat > .env.template << 'EOF'
DATABASE_URL=op://dev/db/url
API_KEY=op://dev/api/key
GOOGLE_CLIENT_SECRET=op://dev/google-oauth/client-secret
EOF

# 3. op run でテンプレート内の op:// 参照を解決し、環境変数として子プロセスに注入
op run --env-file=.env.template -- npm run dev

.env ファイルは不要。テンプレートには op:// 参照のみが書かれているため、リポジトリにコミットしても安全。op run は子プロセスにのみ環境変数を注入するため、親シェルには平文のシークレットが残らない。

B-4. Git認証: 平文の ~/.git-credentials をなくす

今どうなっているか確認:

# macOS / Linux
cat ~/.git-credentials
git config --global credential.helper
# Windows (PowerShell)
Get-Content "$env:USERPROFILE\.git-credentials" -ErrorAction SilentlyContinue
git config --global credential.helper

credential.helper = store になっていたら平文保存。SSH 接続で GitHub を利用している場合(git@github.com: 形式)は HTTPS 認証自体を使わないため、credential.helper の設定は不要。

HTTPS 認証を使っている場合のどう直すか:

# GitHub CLIで認証(推奨。全OS共通。~/.git-credentials が不要になる)
gh auth login
gh auth setup-git

# またはcredential-managerでOSキーチェーンに保管
git config --global credential.helper osxkeychain       # macOS
git config --global credential.helper manager           # Windows(Git Credential Manager)
git config --global credential.helper secretservice     # Linux(GNOME Keyring等)
git config --global credential.helper store             # ← これはNG(平文保存)

B-5. Docker認証: レジストリトークンをキーチェーンに保管

今どうなっているか確認:

# macOS / Linux
cat ~/.docker/config.json | grep -E '"auth"|"identitytoken"'
# Windows (PowerShell)
Select-String -Path "$env:USERPROFILE\.docker\config.json" -Pattern '"auth"|"identitytoken"' -ErrorAction SilentlyContinue

auth フィールドに値が入っていたら平文(Base64)で保存されている。

どう直すか:

~/.docker/config.json(Windowsは %USERPROFILE%\.docker\config.json)を編集し、credsStore を設定する。

// macOS
{ "credsStore": "osxkeychain" }

// Windows(Docker Desktop導入済みならデフォルトで設定されている)
{ "credsStore": "desktop" }

// Linux
{ "credsStore": "secretservice" }
# 設定後に再ログイン
docker logout
docker login

B-6. MFA / SSOの徹底

日常的に使うサービスでMFA(多要素認証)を有効化する。SSOが利用できる環境ではSSOに統合し、個別のパスワード管理をなくす。

今どうなっているか確認:

  • GitHub: Settings > Password and authentication > Two-factor authentication
  • AWS: IAM > Security credentials > Multi-factor authentication (MFA)
  • Google Cloud: Google アカウント > セキュリティ > 2段階認証プロセス
  • Azure: Microsoft アカウント > セキュリティ > 2段階認証

組織でSSO(シングルサインオン)が利用できる場合は、各サービスを IdP(Okta、Microsoft Entra ID 等)に統合し、IdP 側で MFA を強制する方が個別に設定するより確実。

B-7. Terraform state: ローカルに平文で置かない

terraform.tfstate には Terraform が管理する全リソースの現在の状態が記録されており、DBパスワード、APIキー、秘密鍵などが平文で含まれる。デフォルトではカレントディレクトリにローカルファイルとして保存されるため、悪意あるパッケージから容易に読み取れる。

今どうなっているか確認:

# ローカルに tfstate が存在するか
find . -name "terraform.tfstate*"
find ~ -name "terraform.tfstate*" -maxdepth 5

# backend の設定を確認
grep -r 'backend' *.tf

backend が未設定、または backend "local" になっていたらローカル保存。

どう直すか:

方法 仕組み
リモートバックエンド S3、GCS、Azure Blob Storage 等にstateを保管。ローカルにファイルが残らない
state の暗号化 S3 の場合は encrypt = true でサーバーサイド暗号化。GCS はデフォルトで暗号化される
アクセス制御 バックエンドのバケットに最小権限の IAM ポリシーを設定
# GCS バックエンドの例
terraform {
  backend "gcs" {
    bucket = "my-terraform-state"
    prefix = "terraform/state"
  }
}

# S3 バックエンドの例
terraform {
  backend "s3" {
    bucket  = "my-terraform-state"
    key     = "terraform.tfstate"
    region  = "ap-northeast-1"
    encrypt = true
  }
}

加えて、.gitignore*.tfstate*.tfstate.* を追加し、git への混入を防ぐ。terraform.tfvars にシークレットを直書きしている場合も同様に .gitignore に追加し、シークレットは環境変数や -var フラグで渡す。

予防C: 悪意あるパッケージを入れない(全開発者向け)

予防A・Bで「盗まれても被害を最小限にする」対策を示したが、そもそも悪意あるパッケージをインストールしないことが最善。Telnyx侵害ではパッケージのコード自体は一見無害に見え、実行時にC2サーバーからバイナリを取得して初めて悪意ある挙動が発現する方式が使われた。パッケージの中身を事前にレビューしても防げないケースがあることを前提に対策を考える必要がある。

同じ原理はVS Code拡張機能にも当てはまる。拡張機能はインストールするとエディタと同じ権限で動作し、ファイルシステムへのアクセス、ターミナルコマンドの実行、外部への通信が可能。VS Code Marketplaceの審査はnpmやPyPIと同様に限定的であり、人気拡張機能のtyposquatや、正規拡張機能の侵害も報告されている。パッケージと同じ選定基準(名前・公開元・利用規模の確認)を適用し、不要な拡張機能は削除すること。

C-1. パッケージの選定基準

パッケージをインストールする前に、そのパッケージが信頼できるかを確認する。Claude Code等のAIツールに提案されたパッケージも同様。

確認ポイント:

観点 確認方法 注意すべき兆候
名前 レジストリで正確な名前を検索 人気パッケージに似た名前(typosquat)。例: reqeustsrequests の誤字)、node-ipc2
メンテナンス レジストリやGitHubで最終更新日・Issue対応状況を確認 長期間更新がない、Issueが放置されている
利用規模 週間ダウンロード数、GitHubスター数を確認 極端に少ない場合は代替パッケージを検討
公開元 公開者・組織を確認 個人アカウントから突然公開された、公開者の他のパッケージがない
依存関係 そのパッケージ自体の依存数を確認 依存が多いほどサプライチェーンリスクが増大
postinstallスクリプト npm info <package> scripts で確認 インストール時に外部通信やファイル操作を行うスクリプトがある
# npm: パッケージの情報を確認
npm info <package> description maintainers dist-tags time.modified

# npm: 週間ダウンロード数を確認
npm info <package> --json | jq '.dist-tags, .time.modified'

# pip: パッケージの情報を確認
pip show <package>

今回のLiteLLMやTelnyxの事例は既に信頼されたパッケージの侵害であるため、選定基準だけでは防げない。以降のC-2〜C-6で「信頼されたパッケージが侵害された場合」への備えを示す。

C-2. レジストリプロキシ(Takumi Guard)

パッケージマネージャとレジストリの間にセキュリティプロキシを配置し、悪意あるパッケージをインストール前にブロックする。無料・登録不要。PyPIエンドポイントでは72時間の検疫期間を設けている。インストール後に危険と判明した場合のフォローアップ通知もあり(メール登録が必要)。

npm / bun / pnpm / yarn(グローバル設定、全プロジェクトに適用):

npm config set registry https://npm.flatt.tech

# 確認: verbose ログに https://npm.flatt.tech が表示されればOK(実際にはインストールされない)
npm install --dry-run --loglevel verbose some-package

pip(グローバル設定):

pip config set global.index-url https://pypi.flatt.tech/simple/

# 確認: 出力に "Looking in indexes: https://pypi.flatt.tech/simple/" が表示されればOK
pip install --dry-run requests

設定ファイルの場所: ~/.config/pip/pip.conf(macOS / Linux)、%APPDATA%\pip\pip.ini(Windows)

uv(グローバル設定ファイルを直接編集):

設定ファイルの場所: ~/.config/uv/uv.toml(macOS / Linux)、%APPDATA%\uv\uv.toml(Windows)

index-url = "https://pypi.flatt.tech/simple/"
# 確認
cat ~/.config/uv/uv.toml                  # macOS / Linux
type "%APPDATA%\uv\uv.toml"               # Windows

C-3. 検疫期間(minimumReleaseAge / cooldown)

新規バージョンの公開直後にインストールしない「検疫期間」を設ける仕組み。パッケージマネージャや依存関係更新ツールにそれぞれ設定がある。

どこに設定するか 設定方法 備考
npm (v11.10.0+) npm config set min-release-age 7 グローバル設定。v11.10.0 未満では未対応のため npm install -g npm@latest で更新
bun bunfig.toml[install] minimumReleaseAge = 604800 秒指定(604800秒 = 7日)。既にロックファイルで固定済みのパッケージには影響しない
uv (Python) pyproject.tomlexclude-newer = "7 days" プロジェクト単位。グローバルにするなら ~/.config/uv/uv.toml に同様に設定

CI/CD での検疫期間(Renovate、pinact)については予防E を参照。パッケージマネージャ側と CI/CD 側の設定は独立しているため、両方に設定することが推奨されている。

axios侵害では悪性バージョンの露出期間が約2〜3時間だった。7日間の検疫期間を設定していれば、この攻撃はブロックできていた。

C-4. postinstall スクリプトの制御

axios侵害では、悪性の依存パッケージ plain-crypto-js の postinstall フックを経由してマルウェアが設置された。postinstall スクリプトの実行を制御することで、この種の攻撃の発火を防げる。

方法 設定 備考
bun(デフォルトで無効) package.json"trustedDependencies": ["パッケージ名"] bun は依存パッケージの lifecycle script をデフォルトで実行しない。実行が必要なパッケージのみ trustedDependencies で許可する許可リスト方式
pnpm onlyBuiltDependencies package.json"pnpm": { "onlyBuiltDependencies": ["node-gyp-build"] } 許可リスト方式。postinstall を実行するパッケージを明示的に指定する。未指定のパッケージのスクリプトは実行されない
npm --ignore-scripts npm config set ignore-scripts true 全パッケージの lifecycle script を無効化。正当なパッケージ(native addon のビルド等)にも影響するため、必要なパッケージは npm rebuild <package> で個別に実行する
# npm: グローバルに postinstall を無効化
npm config set ignore-scripts true

# 特定パッケージのみ手動でビルド(必要な場合)
npm rebuild bcrypt

C-5. ロックファイルの厳密利用

ロックファイルがあっても npm installpip install はロックを無視して新しいバージョンを入れることがある。CIでは厳密なインストールコマンドを使う。

# npm: npm install ではなく
npm ci

# uv: uv sync ではなく
uv sync --frozen

C-6. 依存関係の棚卸し

使っていない依存が多いほど攻撃面が広がる。定期的に不要な依存を削除する。

# npm: 未使用の依存を検出
npx depcheck

# Python: 脆弱性のある依存を検出
pip-audit

予防D: シークレットをコミットしない(全開発者向け)

.env や鍵ファイルをうっかりコミットしてしまうと、Git履歴に残り続ける。gitleaksを使ってpre-commitフックとCIの両方で多層防御する。

D-1. ツール選定

複数のクレデンシャル検出ツールを比較した結果(gitleaks, TruffleHog, detect-secrets, git-secrets, talisman, secretlint, GitGuardian)、以下の理由でgitleaksを推奨する。

観点 gitleaks
インストール Go製の単一バイナリ。brew install gitleaks で完了
検出パターン 150以上のシークレットパターンを標準装備
外部通信 なし(完全ローカル実行)
Git履歴スキャン 対応
カスタムルール TOML設定で追加可能
メンテナンス 活発(GitHub Stars 24,000超)

学術研究ではrecall 88%(約8件に1件は見逃す可能性)。単一ツールで完璧な検出はできないため、pre-commit(第1層)+ GitHub Push Protection(第1.5層)+ CI(第2層)の多層防御が重要。

D-2. 導入手順

# 1. インストール
brew install gitleaks              # macOS / Linux (Homebrew)
# Windows: winget install Gitleaks  または scoop install gitleaks

# 2. pre-commitフックとして設定(.pre-commit-config.yaml)
cat >> .pre-commit-config.yaml << 'EOF'
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.21.2
    hooks:
      - id: gitleaks
EOF

# 3. pre-commitフレームワークのインストールとフック有効化
pip install pre-commit
pre-commit install

# 4. 既存のGit履歴をスキャン(初回のみ)
gitleaks detect --source . -v

pre-commitフックはプロジェクトごとの設定。グローバルに全リポジトリで有効にするには git config --global core.hooksPath でグローバル hooks ディレクトリを指定する方法があるが、プロジェクトごとの pre-commit フレームワークが無視されるため併用できない。

pre-commitフックは git commit --no-verify でバイパスできるため、CIでもgitleaksを実行する(第2層)。GitHub Push Protectionはサーバーサイドでブロックするためバイパスできない(第1.5層、パブリックリポジトリではデフォルト有効)。

D-3. カスタムルールの追加例

# .gitleaks.toml
[[rules]]
id = "custom-api-key"
description = "Custom API Key"
regex = '''(?i)my_service_api_key\s*=\s*['"][a-zA-Z0-9]{32,}['"]'''
tags = ["key", "custom"]

予防E: ワークフローの安全性を確保(CI/CD運用者向け)

本セクションは「個人的にGitHub Actionsまわりで気をつけていること」がよくまとまっていたため、特に参考にさせていただいた。

Trivy侵害ではImposter Commit(fork経由のcommitを正規のタグに紐づけて、正規のコードに見せかける手法)でGitHub Actionsに悪意あるコードが注入された。タグ参照(@v4等)はこの手法で改竄できるため、変更不可能なcommit SHA(コミットの一意なID)で固定する。

E-1. permissions の明示

permissions:
  contents: read
  pull-requests: write

GITHUB_TOKEN の権限をジョブレベルで最小限に明示する。ワークフローレベルではなくジョブレベルで設定することで、ジョブごとに必要な権限だけを付与でき、ジョブ追加時にも安全。未設定の場合はデフォルトで広い権限が付与される。Trivy侵害ではPATの repo 権限を悪用してデータを外部に持ち出していた。

E-2. commit SHAへのpin留め

# タグ参照(Imposter Commitで改竄可能)
- uses: actions/checkout@v4

# commit SHA固定(推奨)
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

リポジトリ設定の「Require actions to be pinned to a full-length commit SHA」で強制可能。pinact run -u で既存ワークフローを一括変換できる。

E-3. actions/checkoutpersist-credentials: false

- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
  with:
    persist-credentials: false

デフォルトでは checkout 後に GITHUB_TOKEN がローカルの git 設定に残り、後続のステップやサブプロセスからアクセスできる。persist-credentials: false でこれを防ぐ。

E-4. ${{ }} 直接展開の禁止

# 危険(インジェクション可能)
run: echo "${{ github.event.pull_request.title }}"

# 安全(envを介する)
env:
  PR_TITLE: ${{ github.event.pull_request.title }}
run: echo "$PR_TITLE"

E-5. pull_request_target の制限

Trivy初回侵害の起点。forkからのPRでもベースリポジトリのシークレットにアクセスできるため、PRのコードを信頼されたコンテキストで実行しないよう注意する。

E-6. 3rd party Action削減

Git操作やPR作成など、GitHub CLIで代替できるものは自前で実装する。依存するActionが少ないほど攻撃面が縮小する。

E-7. 依存更新の検疫期間

Renovate(依存更新bot)や pinact(Action pin 更新ツール)でも、公開直後のバージョンをすぐに取り込まない検疫期間を設定できる。

ツール 設定方法 備考
Renovate minimumReleaseAge: "7 days" プロジェクト単位。セキュリティアップデートはbypass。internalChecksFilter: strict 推奨
pinact pinact run -u --min-age 7 Action pin の更新時に待機期間を設定

E-8. ブランチ保護

default branchにbranch ruleset / push rulesetを設定し、signed commitを必須化する。リポジトリ設定のIaC化(Terraform等)で一覧性と一貫性を確保。

E-9. コミット署名の検証

Imposter Commit対策。GitHub UIで正規にマージされたcommitには自動署名が付くため、verification.verified: false のcommitがタグに紐づいていたら警戒対象。zizmorはこの検出にも対応している。

ローカルのコミット署名には GPG 署名と SSH 署名(Git 2.34 以降)がある。1Password SSH Agent を使っている場合は SSH 署名の方が鍵管理を一元化できる。

E-10. Linterの導入

上記 E-1〜E-9 の設定を自動検証するツール群。

ツール 主な検出対象
actionlint ワークフロー構文エラー、${{ }} のインジェクションリスク
ghalint セキュリティベストプラクティスの違反(permissions未設定等)
zizmor GitHub Actions固有のセキュリティ問題(unpinned action、dangerous triggers、excessive permissions等)
pinact Actionバージョンのcommit SHAへのpin留め
# まとめて実行する例
pinact run -u --min-age 7
ghalint run
actionlint
zizmor .github/workflows/

Claude Codeのスキルとして設定し、ワークフロー編集後に自動実行させる運用も有効。

予防F: ランナー上のシークレットを守る(CI/CD運用者向け)

Trivy侵害ではRunner.Workerプロセスのメモリダンプでシークレットが抽出された。CI/CDランナーはクレデンシャルが集中する場所であり、侵害時の被害を最小化し、事後追跡を可能にする仕組みが必要。

F-1. Secretsに長期鍵を置かない

  • Google Cloud: WIF(Workload Identity Federation)で接続
  • GitHub App Private Key: Cloud KMS等に保管
  • PATは極力使わない
  • どうしても鍵を置く場合はEnvironment secretsに入れ、protected branchからのみアクセス可能にする

F-2. ローテーションのatomic化

Trivy再侵害では、シークレットのローテーション完了前に攻撃者が新しいトークンを取得した。旧トークンの即時無効化を含むatomicな手順を確立する。

F-3. ランナーの通信ログ記録

ランナーからの外部通信をプロセス単位で記録する。侵害発生後に「何が起きたか」を追跡するための基盤であり、今回のような事例では攻撃者サーバーへの接続有無の確認に直結する。

F-4. Self-Hosted Runnerの隔離

ジョブごとにエフェメラルなコンテナで実行し、ホストマシン上のクレデンシャルへのアクセスを制限する。特に暗号通貨バリデータノードやクラウド管理サーバーとの同居は避ける。

予防G: パッケージ公開を安全にする(パッケージメンテナ向け)

Trivy/LiteLLMいずれもメンテナアカウントの侵害が起点。自分たちが公開するパッケージが同じ手口で乗っ取られないための対策。

G-1. パッケージレジストリアカウントのMFA

Trivy/LiteLLMいずれもメンテナアカウントの侵害が起点。パッケージを公開するアカウントでは MFA を必ず有効化する。

  • npm: npmjs.com > Account > Security
  • PyPI: pypi.org > Account settings > Two factor authentication

G-2. パッケージ公開権限の分離

  • npmでは --provenance 付き公開でビルド元を検証可能にする
  • PyPIではTrusted Publishersを利用し、トークンベースの公開を廃止する
  • パッケージ公開をCI/CDからのみ行い、ローカルからの公開を禁止する

G-3. SBOMの生成と管理

依存関係の可視化のためSBOMを生成・保管する。侵害パッケージが公表されたとき、自プロジェクトへの影響を迅速に判定できる。

開発環境の隔離による封じ込め

予防A〜Gの対策をすり抜けて悪意あるパッケージが実行された場合でも、開発環境自体がホストマシンから隔離されていれば、被害をその環境内に封じ込められる。~/.aws/credentials~/.ssh/id_* がそもそも見えない環境でコードを実行すれば、窃取対象が存在しない。

方式の比較

方式 仕組み ホストからの隔離 通信制御 導入コスト
Claude Code Web Anthropic管理のクラウドVMでセッションごとに隔離実行 完全に隔離。ローカルのファイル・クレデンシャルにアクセスしない 4段階(None / Trusted / Custom / Full)で設定可能 低(ブラウザのみ)
Claude Code サンドボックス OS レベル(Seatbelt / bubblewrap)で Bash の子プロセスのファイル・ネットワークを制限 部分的。ホスト上で動作するが、指定パスへのアクセスをブロックできる 許可ドメインのみ接続可能 低(/sandbox で有効化)
Dev Containers(VS Code) Docker コンテナ内に開発環境を構築し、VS Code がリモート接続 コンテナ内に限定。ホストの ~/.aws 等にアクセスできない Docker のネットワーク設定で制御可能 中(Docker + 設定ファイル)
GitHub Codespaces クラウド上の Dev Container。ブラウザ or VS Code から接続 完全に隔離 クラウド側でネットワークポリシーを設定可能 中〜高(有料、組織設定)

Claude Code Web

Claude Code Web(claude.ai/code)はAnthropicが管理するクラウドVM上でセッションごとに隔離された環境を提供する。ローカルマシンのファイルシステムやクレデンシャルには一切アクセスしない。

セキュリティ上の特徴:

観点 仕組み
実行環境 セッションごとに隔離されたVM。他のセッションやローカルマシンとは完全に分離
ネットワーク 4段階のアクセスレベル(None / Trusted / Custom / Full)を環境ごとに設定可能。Trusted ではパッケージレジストリや GitHub 等の許可ドメインのみ接続できる
認証情報 GitHub 認証はセキュリティプロキシ経由で処理され、認証トークンがVM内に入らない。git push は作業ブランチのみに制限される
ローカルデータ ローカルの ~/.aws~/.ssh~/.config/gcloud 等はVM内に存在しない。リポジトリのクローンのみが利用可能
セッション終了 セッション完了後にVMが自動的に破棄される

サプライチェーン攻撃の観点では、仮に悪意あるパッケージがVM内で実行されても、窃取対象のクレデンシャルがVM内に存在せず、C2サーバーへの通信もネットワークポリシーでブロックされるため、被害を封じ込められる。

注意点として、Claude Code Web のVM内にはローカルマシンのユーザーレベル設定が存在しない。以下の設定はクラウドセッションに引き継がれないため、プロジェクトレベルでの管理またはセットアップスクリプトでの再設定が必要になる。

引き継がれない設定 ローカルでの設定場所 クラウドでの対応
Claude Code の設定(フック、権限等) ~/.claude/settings.json リポジトリの .claude/settings.json にコミットする
CLAUDE.md ~/.claude/CLAUDE.md リポジトリの .claude/CLAUDE.md にコミットする
npm レジストリプロキシ ~/.npmrc プロジェクトルートの .npmrc にコミットする
npm 検疫期間 npm config set min-release-age 7(グローバル) .npmrcmin-release-age=7 を記載してコミットする
bun の設定(検疫期間等) ~/.bunfig.toml プロジェクトルートの bunfig.toml にコミットする
pip / uv レジストリプロキシ ~/.config/pip/pip.conf~/.config/uv/uv.toml セットアップスクリプトで設定するか、プロジェクトの pyproject.toml で管理する

ただし、ネットワーク制御には構造的な限界がある。サプライチェーン攻撃ではマルウェアの配布やデータの送信先として GitHub(github.com)を利用するケースが多い。GitHub はパッケージレジストリや CI/CD の動作に必要であるため、許可ドメインから外すことが難しく、ドメイン単位のフィルタリングだけでは防ぎきれない。Trivy侵害でもGitHub Actionsのログを窃取データの持ち出し経路として悪用していた。この点は Claude Code Web に限らず、サンドボックスや Dev Containers のネットワーク制御にも共通する課題である。

Claude Code のサンドボックス

ローカルで Claude Code を使う場合でも、サンドボックス機能でファイルシステムとネットワークを OS レベルで制限できる。完全な環境隔離(Dev Containers 等)とは異なり、Bash コマンドとその子プロセスに対する制限であり、ホスト上で動作する点は変わらない。ただし、悪意あるパッケージの postinstall スクリプトは Bash 経由で実行されるため、サプライチェーン攻撃に対しては有効に機能する。

有効化:

/sandbox

Claude Code 内で /sandbox を実行するとモード選択メニューが表示される。対応プラットフォームは macOS、Linux、WSL2。ネイティブ Windows は未対応(対応予定)。

仕組み:

制限 内容 OS実装
ファイルシステム 作業ディレクトリとそのサブディレクトリにのみ書き込み可能。それ以外は読み取り専用 macOS: Seatbelt、Linux/WSL2: bubblewrap
ネットワーク 許可されたドメインのみ接続可能。未許可のドメインへの接続はブロックされ、確認プロンプトが表示される プロキシサーバーによるドメイン制限

設定例(settings.json):

{
  "sandbox": {
    "enabled": true,
    "filesystem": {
      "denyRead": ["~/.aws", "~/.ssh", "~/.config/gcloud"],
      "allowWrite": ["."]
    },
    "allowUnsandboxedCommands": false
  }
}
  • denyRead: クレデンシャルの格納パスを読み取り禁止にすることで、postinstall スクリプトからの窃取を防ぐ
  • allowWrite: 書き込みを作業ディレクトリに限定する(デフォルト動作)
  • allowUnsandboxedCommands: false: サンドボックス外での実行を完全にブロックする(デフォルトでは、サンドボックス制限でコマンドが失敗した場合にサンドボックス外での再実行を許可するエスケープハッチがある)

その他、.claudeignore による機密ファイルの除外、カスタムフックによる監査、組織向けのマネージド設定による一括ポリシー適用なども設定できる。詳細は Claude Codeで行うべきセキュリティ設定 10選 を参照。

Dev Containers の導入例

リポジトリに .devcontainer/devcontainer.json を追加すれば、チーム全員が同じ隔離環境を利用できる。

{
  "name": "dev",
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu",
  "features": {
    "ghcr.io/devcontainers/features/node:1": {}
  },
  "mounts": [],
  "runArgs": ["--network=dev-net"]
}

mounts を空にすることでホストのホームディレクトリがマウントされず、クレデンシャルがコンテナ内に存在しない状態を作れる。必要なシークレットのみ環境変数として渡す運用にすれば、窃取される情報を最小限にできる。

隔離環境でのクレデンシャルの扱い

隔離しただけではクラウドやDBへの接続ができなくなる。必要なクレデンシャルだけを限定的に渡す方法を組み合わせる。

  • 短命トークンを都度発行し、環境変数で渡す(予防Aの方針に沿う)
  • Dev Container の initializeCommandaws sso logingcloud auth login を実行し、認証情報をコンテナ内にのみ保持
  • 1Password CLI(op run)でコンテナ内のプロセスにのみシークレットを注入

検知と事後対応

現実的な検知の難しさ

悪意あるパッケージの実行をリアルタイムに検知することは、現実的には難しい。

手段 できること 限界
EDR(CrowdStrike、SentinelOne等) プロセスの挙動・通信・ファイル操作を常時監視し、不審な振る舞いをリアルタイム検知 組織導入が前提で個人には高コスト。新しい手法には対応が遅れる場合がある
アプリケーションファイアウォール(LuLu、Little Snitch等) プロセス単位で外部通信を監視し、未知の接続を通知・ブロック 外部通信を伴わない攻撃(ファイル改竄、永続化のみ等)は検知できない
DNS監視(NextDNS等) typosquatドメインへのアクセスを検知・ブロック 攻撃者がIPアドレス直接指定で通信した場合は検知できない
npm audit / pip-audit 既知の脆弱性を検出 CVE が登録されるまで検知できない。サプライチェーン侵害は公表まで時間がかかる

個人〜中小規模チームの現実的な落とし所は、予防と封じ込め(前セクションまでの対策)に力を入れ、検知は「侵害が公表された際の影響確認と事後対応」に割り切ること。組織にEDRが導入されている場合はその検知能力に依存できるが、それがない環境ではセキュリティニュースの追跡と迅速な影響確認が主な手段になる。

侵害が公表されたときの影響確認

侵害パッケージが公表された際に、自分の環境が影響を受けているかを確認する手順。

ネットワーク接続の確認

# macOS
lsof -i -nP | grep ESTABLISHED
# Linux
ss -tunap | grep ESTAB
# Windows (PowerShell)
Get-NetTCPConnection -State Established | Select-Object RemoteAddress, RemotePort, OwningProcess |
  ForEach-Object { $_ | Add-Member -NotePropertyName "ProcessName" -NotePropertyValue (Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName -PassThru }

今回の攻撃では models.litellm.cloudscan.aquasecurtiy.org(typosquat)への通信が発生していた。Telnyx侵害では 83.142.209.203:8080 へのC2通信が確認されている。axios侵害では sfrclak[.]com:8000(IP: 142.11.206.73)へのC2通信が確認されている。侵害レポートに記載されたIoC(Indicator of Compromise: 通信先IP・ドメイン、ファイルハッシュ等の侵害の痕跡情報)と照合する。

永続化ファイルの確認

# macOS / Linux
# LiteLLM侵害の永続化ファイル
ls -la ~/.config/sysmon/sysmon.py
ls -la ~/.config/systemd/user/sysmon.service
ls -la /tmp/pglog /tmp/.pg_state

# axios侵害のマルウェアファイル
ls -la /Library/Caches/com.apple.act.mond    # macOS
ls -la /tmp/ld.py                             # Linux

# 不審なcron/systemdサービスの確認
crontab -l
systemctl --user list-units --type=service | grep -v "loaded inactive"

# 最近変更された不審なファイル(直近24時間)
find ~/.config -name "*.py" -mtime -1
find ~/.config/systemd -name "*.service" -mtime -1
# Windows (PowerShell)
# Telnyx侵害の永続化ファイル(スタートアップフォルダに msbuild.exe として配置される)
$startup = [Environment]::GetFolderPath("Startup")
Test-Path "$startup\msbuild.exe"
Test-Path "$startup\msbuild.exe.lock"

# axios侵害のマルウェアファイル
Test-Path "$env:PROGRAMDATA\wt.exe"

# 不審なスタートアップ登録・スケジュールタスクの確認
Get-CimInstance Win32_StartupCommand | Select-Object Name, Command, Location
Get-ScheduledTask | Where-Object { $_.State -eq "Ready" } | Select-Object TaskName, TaskPath

# 直近24時間に変更されたスクリプトファイル
Get-ChildItem "$env:APPDATA" -Recurse -Include "*.py","*.ps1","*.bat" -ErrorAction SilentlyContinue |
  Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-1) }

プロセスの確認

# macOS / Linux
ps aux | grep -E "python.*base64|python.*exec|sysmon"
ps aux | grep -E "python|node" | grep -v grep
# Windows (PowerShell)
Get-Process python*, node* -ErrorAction SilentlyContinue | Select-Object Id, ProcessName, Path, CommandLine

Kubernetes環境の確認

# kube-system に不審なPodがないか
kubectl get pods -n kube-system | grep node-setup

LiteLLM侵害では node-setup-* という名前の特権Podが全ノードにデプロイされた。

事後対応の手順

侵害が確認された場合:

  1. 悪意あるパッケージをアンインストールし、キャッシュも削除する(例: pip uninstall litellm && pip cache purge
  2. 永続化ファイルを削除する(上記の確認コマンドで見つかったもの)
  3. 窃取対象の一覧(本文書冒頭)を参照し、該当するクレデンシャルを全てローテーションする
  4. Git履歴やシェル履歴にシークレットが含まれていないか確認する

さらに踏み込んだ多層防御

本記事で紹介した対策は主にクレデンシャル保護・パッケージ管理・環境隔離に焦点を当てているが、AIエージェントの実行環境をより本格的に守るには、以下のような多層防御も有効。導入・運用の難易度は上がるため、チームの技術力や運用体制に応じて検討する。

目的 OSSツール例
プロセス隔離 コンテナやサービスの権限を最小化し、侵害時の横展開を防ぐ Docker hardening(cap_drop, read_only, no-new-privileges)、systemd サンドボックス、Firejail
マルウェア検知 ワークスペース内に配置されたマルウェアを検出する ClamAV(on-access スキャン対応)、rkhunter(ルートキット検出)
ランタイム検知 プロセスの異常行動をカーネルレベルで検知する(OSS版EDR相当) Falco(eBPFベース)、Wazuh(HIDS + ファイル整合性監視 + ログ分析)
ネットワーク制御 プロセスやユーザー単位で外部通信を制限する nftables(UID単位のアウトバウンド制限)、iptables(コンテナの通信制限)
バックアップ ランサムウェアや誤操作からの復旧手段を確保する Btrfs / ZFS スナップショット、rsync による immutable バックアップ

商用EDRがない環境でも、Falco + Wazuh の組み合わせでランタイムの異常検知とホスト全体の監視が実現できる。具体的な設定手順は 無料のOSSだけでOpenClawのセキュリティを本気で固める方法 が参考になる。AIエージェントランタイム(OpenClaw)向けの記事だが、ローカルでAIコーディングツールを運用する環境にも応用できる。

まずやることチェックリスト

今すぐ(30分以内):

  • MFA を有効化する(GitHub / AWS / Google Cloud / Azure)(B-6)
  • ~/.aws/credentials に静的キーがないか確認する(B-1)
  • npm / pip のレジストリプロキシを設定する(C-2)
  • npm / bun の検疫期間を設定する(C-3)

次にやる(半日〜):

  • postinstall スクリプトの制御を設定する(C-4)
  • SSH 鍵を 1Password SSH Agent に移行する(B-2)
  • aws-vault / SSO 化を進める(B-1)
  • クラウド認証をログアウト運用に切り替える(A-2)
  • gitleaks を導入する(D-2)
  • GitHub Actions の SHA pin を確認する(E-2)

有識者と相談して進める:

  • クレデンシャルと権限の棚卸し(予防A)
  • ロール / SA 借用への切替(A-1)
  • .env の保護方式の選定と導入(B-3)
  • 開発環境の隔離の検討(Claude Code Web / サンドボックス / Dev Containers)

おわりに

本記事で紹介した対策の多くは、セキュリティの世界では以前から言われてきた当たり前のことである。MFA を有効にする、静的キーを置かない、権限を最小限にする、使い終わったらログアウトする。特別なことは何もない。

ただ、当たり前のことを当たり前にやり続けるのは難しい。1つの設定漏れ、1つの放置されたクレデンシャルが、サプライチェーン攻撃の被害を決定的にする。今回の Trivy / LiteLLM / Telnyx / axios の事例は、攻撃者がその「漏れ」を正確に突いてくることを示した。

AIコーディングツールの普及で、パッケージのインストールはこれまで以上にカジュアルになった。だからこそ、基本的な管理を徹底することの重要性は増している。完璧を目指す必要はないが、チェックリストを一つずつ潰していくことが、自分の環境を守る確実な一歩になる。

参考

9
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?