AIエージェント時代のnpmサプライチェーン対策をAGENTS.mdで補強する

AI

はじめに

前回の記事では、2026年3月のaxiosサプライチェーン攻撃をきっかけに、.npmrcignore-scriptsmin-release-age をプロジェクトとグローバルの両方に入れて開発環境をハードニングする手順をまとめました。

ところがその後、業務でも個人開発でもAIコーディングエージェント(Claude Code、Codex、Multicaなど)に依存関係の追加やライブラリのアップグレードを任せる場面が一気に増えてきました。エージェントは便利な反面、エラーメッセージを読んで「ブロックされたフラグを外す」「設定ファイルそのものを書き換える」といった迂回行動を取ることがあり、前回入れた対策が想定外に無効化されるケースに遭遇しました。

加えて、エージェントを開発用コンテナの中で動かす運用にすると、ホストの ~/.npmrc はコンテナの中までは届きません。コンテナ内では別の世界が立ち上がっており、そこにも同じガードが必要になります。

本記事では、前回の .npmrc 対策を前提として、(1)コンテナ環境への設定の伝搬、(2)AGENTS.md を使ったエージェントへの行動指針の明文化、という2つの補強策を実例つきで紹介します。本記事は自身の管理下にある開発環境のハードニングを目的としており、防御側の文脈に限定して書いています。

なぜ追加の防御層が必要なのか

.npmrc のグローバル設定はホームディレクトリまで遡って参照されるため、人間がターミナルから直接 npm install を叩く運用なら一定の効果があります。npmは設定ファイルをカレントディレクトリから順に上位ディレクトリへ遡り、最後にユーザーごとのファイル、グローバルファイル、組み込みファイルの順でマージするためです。

一方、AIエージェント開発では事情が変わります。以下の3つの境界で「設定が届かない」「設定を上書きされる」リスクが発生します。

  • コンテナ境界: VS Code Dev Container、Docker、Podmanで隔離した環境では、ホストの ~/.npmrc はそのままでは見えません。コンテナをまっさらな状態で立ち上げ直したり、エージェントごとに別コンテナにする運用では特に顕在化します。
  • 環境変数による上書き: npmは NPM_CONFIG_* 環境変数の方を .npmrc よりも優先します。NPM_CONFIG_IGNORE_SCRIPTS=false.bashrcdocker run -e に紛れ込ませると、ファイル側の設定は無視されます。
  • エージェントの迂回行動: コーディングエージェントは npm install でエラーが出ると、エラーメッセージを手がかりに「とりあえずスクリプトを実行できるようにしよう」と判断することがあります。.npmrc を直接編集する、--ignore-scripts=false を渡す、npm config set で上書きする、といった迂回はどれも数行のコード変更で起こります。

つまり前回の .npmrc だけでは、コンテナ起動とエージェントの自律的なリトライという2つの軸に対して防御が薄いままです。本記事ではここに「コンテナ内へ設定を確実に届ける」「エージェントが迂回しないように指示を残す」という2層を足します。

コンテナ環境に設定を伝搬させる

開発コンテナでは、ホストの ~/.npmrc明示的に持ち込む 必要があります。前回もDockerfileの例は載せましたが、エージェント駆動の開発フローでは「コンテナを使い捨てて頻繁に作り直す」運用が多く、毎回の構築フローに組み込んでおくことが重要になります。

Dev Containerに .npmrc を埋め込む

VS Code Dev Containerを使う場合、.devcontainer/devcontainer.json.npmrc をコンテナにマウントする方法がもっとも軽量です。

{
  "name": "node-dev",
  "image": "mcr.microsoft.com/devcontainers/javascript-node:22",
  "mounts": [
    "source=${localEnv:HOME}/.npmrc,target=/home/node/.npmrc,type=bind,readonly"
  ],
  "containerEnv": {
    "NPM_CONFIG_IGNORE_SCRIPTS": "true",
    "NPM_CONFIG_MIN_RELEASE_AGE": "7"
  }
}

mounts でホストの ~/.npmrcreadonly でマウントすることで、コンテナの中から書き換えできなくしています。さらに containerEnv で同じ設定を環境変数として渡しているのがポイントで、npmは環境変数を .npmrc よりも優先するため、仮に誰かがコンテナ内で ~/.npmrc を別の場所に作り直しても環境変数側の値が適用されます。

注意:マウントだけだと、コンテナ内のエージェントが新規ファイルとして ./.npmrc をプロジェクト直下に作って優先させてしまう余地が残ります。環境変数を併用することでこの抜け道も塞げます。

Dockerfileでイメージ自体にベークする

CI/CDや本番ビルド、もしくは複数人で共有するイメージでは、Dockerfile側に設定を書き込んでしまうのが確実です。

FROM node:22-slim

# システム側の npmrc を直接書き込む
RUN printf 'ignore-scripts=true\nmin-release-age=7\naudit-level=high\n' \
  > /usr/etc/npmrc

# 環境変数で二重に固定する。
ENV NPM_CONFIG_IGNORE_SCRIPTS=true \
    NPM_CONFIG_MIN_RELEASE_AGE=7

WORKDIR /app
COPY package*.json ./

RUN npm ci

COPY . .

npm config get で実効値を点検する

設定が想定通り効いているかは、コンテナ起動後に以下のコマンドで確認できます。

npm config get ignore-scripts
npm config get min-release-age
npm config ls -l | grep -E 'ignore-scripts|min-release-age'

注意:min-release-agenpm config get では確認できない場合がある(npm 11.14.0 未満)

npm 11.14.0 未満では、設定の読み込み(flatten)時に min-release-age が日時に変換されて before として保持され、元の min-release-age キーが削除されます(npm/cli#9199)。 そのため npm config get min-release-age は値が設定されていても null を返します。 該当バージョンを使用している場合は、設定が有効かどうかを npm config get before で確認してください。 この問題は npm 11.14.0 で修正されています。

最後の ls -l は、設定の 由来 (プロジェクト・ユーザー・グローバル・組み込み・環境変数のどれか)を一覧できるため、上書きの調査に役立ちます。エージェントに npm install を任せる前に1回流しておくと、想定外の優先順位ハマりを未然に防げます。

出力例 由来
; "project" config from ./.npmrc プロジェクト直下のファイル
; "user" config from /home/node/.npmrc ユーザーホームのファイル
; "global" config from /usr/etc/npmrc システム全体の組み込み設定
; environment NPM_CONFIG_* 環境変数

AGENTS.mdでエージェントの迂回行動を抑止する

AGENTS.md はリポジトリ直下に置くことで、Claude Code・Codex・Cursor・Aiderなどのコーディングエージェントが起動時に参照する共通のプロジェクト指針ファイルです。Claude Code固有の CLAUDE.md を持っているプロジェクトでは、AGENTS.md をシンボリックリンクで兼用するか、両方に同じ内容を置く運用が広がっています。

エージェントは指示が明示されていれば素直に従う一方、書かれていないことについては自分で判断して動きます。npm install でエラーになったときに「.npmrc を消せばいいんじゃないか」と判断してしまうのは、まさに指示が欠落しているからこそ起こる挙動です。AGENTS.mdやってはいけないこと をはっきり書いておくことで、この判断を「禁止された」と認識させられます。

AGENTS.mdへの追記例

筆者は前回の対策を入れたあと、各リポジトリの AGENTS.md の末尾に以下のようなセクションを追加するようにしました。

## 依存関係の追加・更新時のルール

### 守るべきこと

- `npm install <pkg>``EUSAGE``min-release-age` のエラーが出た場合は、
  原因を本人(ユーザー)に確認してから次の手を打つこと。勝手にバージョンを
  下げたり別パッケージに差し替えたりしない。
- 追加するパッケージは、初出時に以下を確認して短くサマリを残すこと。
  - 週間ダウンロード数(10万未満なら必ず確認する)
  - 直近の公開日(7日未満は基本的に避け、必要ならユーザーに相談する)
  - メンテナと所属組織、GitHubリポジトリへのリンク
- ライフサイクルスクリプト(postinstall, preinstall, prepare)を持つ
  パッケージを追加する場合は、@lavamoat/allow-scriptsなどを使って
  どのスクリプトを許可するか明示的に指定する

### やってはいけないこと

- `.npmrc``ignore-scripts` `min-release-age` `audit-level`
  `false` や緩い値に書き換えること。これらは攻撃対策として全社的に
  決めた値で、エージェント側で勝手に解除しない。
- `--ignore-scripts=false``--force``--legacy-peer-deps`
  「とりあえずインストールを通すため」に使うこと。
- `npm config set ignore-scripts false` のように、設定を実行時に
  上書きすること。
- `NPM_CONFIG_*` 環境変数で `.npmrc` の設定を打ち消すこと。
- 公式レジストリ以外のURLを `registry=` に書くこと。

### エラー時のテンプレ報告

ブロックされた場合は、独断で迂回せず、以下のフォーマットで報告すること。

- インストールしようとしたパッケージ名とバージョン
- 出たエラーメッセージ(先頭10行)
- 想定原因(`ignore-scripts``min-release-age`/レジストリの可否)
- 取りうる選択肢(待つ/許可リストに追加する/別ライブラリに切り替える)

なぜ「禁止リスト」を明示するのが効くのか

エージェントの行動原理は、指示の有無で大きく変わります。プロンプトに明示された制約は強力に尊重する一方、書かれていない領域については「ゴールに向かって最短経路を取る」という最適化が働きます。npm install が失敗したとき、エラーログから読み取れる解決手段(フラグの追加、設定の書き換え)は、エージェントから見れば自然な次の一手です。

ここに「これは禁止」「ブロックされたら相談」と明文化しておくと、エージェントは禁止行動を選択肢から外し、相談という別の経路に切り替えます。AGENTS.md は単なるリポジトリの説明ではなく、エージェント向けの「コーディングガイドライン」として機能させるのが効果的です。

加えて、エージェント以外の人間の新規メンバーにも同じ情報が伝わるという副次効果があります。設定値の意図がドキュメント化されることで、半年後の自分が min-release-age=7 を見て「なんでこれが入ってるんだっけ?」と外してしまう事故も防げます。

CLAUDE.md・.cursorrules との統合

筆者の環境では、Claude Code用に CLAUDE.md を持っているリポジトリが多く、これに AGENTS.md を追加するとファイルが分散します。実運用では以下のように1ファイルにまとめてシンボリックリンクで兼用するのが扱いやすかったです。

# 実体は AGENTS.md にまとめる
ln -s AGENTS.md CLAUDE.md

# Cursor を使う場合
ln -s AGENTS.md .cursorrules

AGENTS.md を正にしておけば、内容の二重管理が発生せず、エージェントを乗り換えても同じガイドラインが効きます。プロジェクトのルートに置くと、サブディレクトリで作業しているエージェントにも自動的に読まれます。

多層防御の全体像

ここまでで導入した4つのレイヤーを整理すると、以下のような重なりになります。

レイヤー 役割 上書き難度
プロジェクトの ./.npmrc デフォルトポリシーの明示。Gitで共有される 低(ファイル編集で外せる)
ホストの ~/.npmrc ターミナル直叩き時の最終防衛 低(個人の判断で変更可)
Dockerfile の /usr/etc/npmrc + ENV コンテナ内での標準値 中(イメージ再ビルドが必要)
AGENTS.md の禁止リスト エージェントが迂回しないようにする規範 高(プロンプトで否定するのが難しい)

それぞれ単独ではすり抜けるパスがありますが、4層を重ねることで「うっかり迂回」「環境差による設定漏れ」のリスクを大幅に下げられます。

実際に試してみて感じたのは、AGENTS.md のレイヤーが入ったことでエージェントの振る舞いが目に見えて変わったということです。以前は npm install がブロックされると数往復で .npmrc を消そうとする提案を出してきましたが、禁止項目を書いたあとは「ブロックされたので相談したい」というフォーマット通りの報告が返ってくるようになりました。

ハマったポイント

環境変数のスペルミスで設定が効かない

NPM_CONFIG_MIN_RELEASE_AGENPM_CONFIG_MIN-RELEASE-AGE のようにハイフンで書いてしまい、設定が無視されていたことに気付くまで30分ほどかかりました。npmは未知の NPM_CONFIG_* 環境変数を エラーにせず無視する ので、ハイフンとアンダースコアの取り違えはサイレントに失敗します。npm config ls -l で実効値を確認するクセを付けたほうが安全です。

npm config set がプロジェクト内 .npmrc を生成する

エージェントが npm config set ignore-scripts false を実行すると、デフォルトでは プロジェクト直下.npmrc に書き出されます。組み込みやグローバル設定より優先されるため、せっかく入れたガードが一気に無効化されます。AGENTS.md の禁止リストにこの形を明示的に書いておくことが大事だと、実例で痛感しました。

まとめ

前回の .npmrc 対策を起点に、AIエージェント時代の開発フローに合わせて2つの補強策を入れました。コンテナ環境にはマウント・環境変数・ビルトインnpmrcの3点で設定を確実に届け、AGENTS.md にはエージェントが取り得る迂回行動を明示的に禁止リスト化する、という構成です。

実際に運用してみると、AGENTS.md の存在がエージェントの「自走しすぎ」を抑える効果は想像以上でした。ガードレールはツール側だけでなく、エージェントに読ませるドキュメントにも書いておくと、防御層が一段増える感覚があります。

一方で、AGENTS.md はあくまでエージェントが指示を尊重することを前提にした「規範的な防御」であり、悪意ある第三者が同じファイルを書き換えて指示を反転させる可能性は別途考える必要があります。リポジトリの保護ブランチ運用、PRレビュー、コード署名といった既存の管理体系と組み合わせて初めて成立する性質のものだ、と理解しておくのが安全です。

次は、AGENTS.md の内容をテンプレートとして社内・個人プロジェクト横断で配布する仕組み(Gitリポジトリのテンプレート、もしくは独自CLIでの同期)を作ってみたいと思っています。 AIエージェントの普及に伴って、こういった「AIエージェント向けのベストプラクティス集」がますます重要になってきそうです。

コメント

タイトルとURLをコピーしました