npmrcを放置しない。axios侵害から見直すnpmのベストプラクティス

技術発信
この記事は約11分で読めます。

2026年3月31日、axiosの1.14.1と0.30.4が侵害され、悪意ある依存関係plain-crypto-js@4.2.1が混入しました。最初に広く知られたのは侵害報告issueで、配布時間やRATの内容、安全なバージョンは1.14.0と0.30.3で、攻撃経路はメンテナーのpostmortemで整理されています。

この話で改めて見えたのは、.npmrcをちゃんと書いて防御しましょうということです。依存関係をどれくらい自動で受け入れるか、どこまでinstall scriptを許すか、公開直後のパッケージをすぐ拾うかを決める、チームのポリシーファイルでもあるので、これを機にちゃんと見直しを行うという趣旨のが今回の趣旨です。

まず押さえたい、.npmrcの置き場所

npmは複数の設定ソースを持ちます。優先順位はざっくり、CLI引数、環境変数、プロジェクトの.npmrc、ユーザーの~/.npmrc、グローバル設定の順です。この仕様はnpm公式docsのConfigにまとまっています。

チームで共有すべきものはプロジェクト直下の.npmrcに置きます。

アプリケーション開発で最初に入れたい設定

まずは次のような最小構成から始めるのが現実的です。ここで使う設定はnpm公式docsのengine-strictsave-exactignore-scriptsauditaudit-levelとして定義されています。

engine-strict=true
save-exact=true
ignore-scripts=true
audit=true
audit-level=high

engine-strict=true

Node.jsやnpmの前提を外した状態でinstallが進むのを止めます。環境差分をとりあえず–forceで通す運用に寄せないための設定です。
根本的な対策ではありませんが、危険な例外運用を減らします。

save-exact=true

直接依存のバージョンを^1.2.3ではなく1.2.3で保存します。lockfileがある以上それだけで完全再現できるわけではありませんが、package.jsonを更新したら意図せず別バージョンを拾った、という事故は減らせます。

ignore-scripts=true

preinstall、install、postinstallなどのライフサイクルスクリプトを止めます。今回のaxios侵害のように、悪意ある依存がインストール時の副作用を持つタイプの攻撃に対しては、かなり強い緩和策になります。

ただし、これは万能ではありません。esbuild、sharp、prisma、playwrightのようにinstall scriptを前提とするパッケージは壊れます。import時に悪意あるコードを実行するライブラリまでは防げません。

必要なものだけ許可したいなら、普段は止めたままにして、CIで許可リストに入れたパッケージだけ再実行するほうが安全です。

GitHub Actionsでもここはよく詰まります。npm ci自体は通っても、その後のbuildやtestでesbuild、sharp、prisma、playwrightが不足状態になって落ちる、という流れになりがちです。ジョブ全体で無効化するより、必要なstepだけnpm_config_ignore_scripts=falseを付けるか、npm rebuildを明示するほうが制御しやすいです。

npm ci
npm_config_ignore_scripts=false npm rebuild esbuild sharp prisma

audit=trueとaudit-level=high

既知の脆弱性に対する最低限の検査です。既知CVEには効きますが、axiosのように、侵害されていても脆弱性情報としてまだ案内されていない、短時間の不正公開には追いつけません。あくまで防波堤と考えるべきです。

npm11なら追加したい設定

npm11系を使っているなら、.npmrcだけで一段階強くできます。ここで効くのはnpm公式docsのallow-gitmin-release-ageです。

allow-git=none
min-release-age=1

allow-git=none/root

gitのURL依存を禁止します。git依存はregistryのスキャン、dist-tag、署名、provenanceの利点から外れやすく、依存パッケージの統制が急に難しくなります。どうしてもルートのpackage.jsonだけで許したいならrootも候補です。

この設定はnpm11系の機能で、npm9とnpm10の設定一覧には出てきません。ローカルではnpm11でも、GitHub Actions側が古いNodeとnpmで動いていると、このポリシーは効きません。CI側もactions/setup-nodeで合わせて固定しておく必要があります。

min-release-age=day

公開された直後のパッケージをインストール対象から外します。axiosの不正なバージョンは約3時間で撤去されました。こういう公開から撤去までが短い事故には、冷却期間を設けるだけで効く場面があります。

値はチーム次第ですが、一般的なアプリケーションなら1日、より慎重にしたいなら7日あたりが候補です。もちろん、新しい修正済みバージョンをすぐ入れたいときには邪魔になるので、セキュリティ修正の緊急度と運用コストのバランスは必要です。

なおnpm公式docsによると、min-release-ageは日数ベースの相対指定です。beforeによる指定は日時ベースの絶対指定です。両者は同時に使えません。

min-release-ageとbeforeは、止め方の向きが違います。前者は公開直後を一定期間まとめて避け、後者は指定時刻より後をまとめて切り落とします。

事故対応で役立つ設定

依存パッケージ経由の侵害が発生した直後は、今から新しく解決する依存を、ある時刻以前に固定する動きが有効です。そのときに使えるのがbeforeです。これはnpm9、npm10、npm11で使えます。

たとえばincident responseの短期対応としては、次のように侵害されたバージョンが出る前の時刻で解決を止められます。

npm ci --before=2026-03-31T00:20:00Z

ずっと使う設定にするものではありませんが、障害対応の手段として知っておく価値があります。

private registryを使うなら、.npmrcはさらに重要

社内proxy registryやartifact managerを使うなら、プロジェクトの.npmrcでどこから取るかを明確にします。ここで効くのはnpm公式docsのomit-lockfile-registry-resolvedreplace-registry-hostです。

@your-org:registry=https://registry.example.com
omit-lockfile-registry-resolved=true
replace-registry-host=always

omit-lockfile-registry-resolved=trueはlockfileにtarball URLを焼き付けない設定です。次回install時に現在のregistry設定で解決し直すため、社内registryに検査やキャッシュを集約しやすくなります。代わりにinstallは少し遅くなります。

replace-registry-host=alwaysはlockfile上のhostを設定したregistryへ寄せたいときに使います。社内mirrorを正とするなら相性が良いですが、OSSプロジェクトでは不要なことも多いです。

認証トークンはここに直接書かず、~/.npmrcまたはCIのsecretで渡します。

ライブラリを公開する側ならprovenanceを使う

axiosのpostmortemでは、再発防止策としてOIDC Trusted Publishingとprovenance attestationの採用が挙がっていました。これは消費者側よりも公開側の設定ですが、.npmrcで触れるならnpm公式docsのprovenanceです。

provenance=true

これはnpm9、npm10、npm11で使えます。効果が出るのは、対応したCIからnpm publishするときです。ローカル端末で長期トークンを使ってpublishする運用をやめ、CIとOIDCに寄せることが本質です。axiosのpostmortemでも、今後の対策としてOIDC flow for publishingとimmutable release setupへの移行が挙がっています。

trusted publishingとprovenanceの関係は、GitHub Actionsの権限設定とnpm側の検証をひとつの流れで見ると整理しやすいです。

GitHub Actionsでtrusted publishingを使うなら、npm公式のtrusted publishing docsを確認してください。OIDC docsにある通り、publish jobにはid-token: writeが必要です。npm公式docsでは、npm11.5.1以降とNode22.14.0以降が前提です。対象はGitHub-hosted runnerです。さらに、public packageをpublic repositoryからpublishする場合に限って、provenanceが自動生成されます。private repositoryからのpublishでは付きません。

permissions:
  contents: read
    id-token: write
    
    steps:
      - uses: actions/setup-node@v6
        with:
          node-version: '24'
          registry-url: 'https://registry.npmjs.org'
      - run: npm --version

trusted publishingが面倒を見るのはnpm publishだけです。詳しくはnpm公式のtrusted publishing docsを参照してください。GitHub Actionsでprivate packageをnpm ciする場合は、別途read-only tokenを使う構成が案内されています。

.npmrcだけでは足りないこと

.npmrcは強いですが、単独では足りません。最低でも次はセットで考えたいです。

  • CIはnpm installではなくnpm ciを使う
  • package-lock.jsonを必ずコミットする
  • npx some-tool@latestをCIで安易に使わない
  • DependabotやRenovateにcooldownを入れる
  • publishはローカル端末ではなくCIから行う

RenovateやDependabotのcooldownも、.npmrcのmin-release-ageと同じ発想で、公開直後のものをすぐ信頼しないための時間を作るための運用です。

設定の対応状況

設定用途npm9npm10npm11メモ
ignore-scriptsinstall scriptを止める対応対応対応npm run本体は明示実行される
save-exact直接依存を固定する対応対応対応save-prefix=””の代わりとして使いやすい
provenancepublish時にprovenanceを付ける対応対応対応対応CIでのpublish前提
omit-lockfile-registry-resolvedlockfileのresolved URLを省く対応対応対応再解決が増えるぶんinstallは遅くなりやすい
replace-registry-hostlockfileのhostをregistryに寄せる対応対応対応internal registry運用向け
before指定時刻以前の公開物に固定する対応対応対応incident responseで便利
allow-gitgit依存を制限する非対応非対応対応noneかrootを検討
min-release-age公開直後のバージョンを避ける非対応非対応対応beforeと併用不可

いま書くなら、こう始める

npm9とnpm10系なら、まずは次で十分です。

engine-strict=true
save-exact=true
ignore-scripts=true
audit=true
audit-level=high

npm11系なら、ここにallow-git=noneとmin-release-age=1を足して以下になります。

engine-strict=true
save-exact=true
ignore-scripts=true
audit=true
audit-level=high
allow-git=none
min-release-age=1

axiosの件で分かったのは、人気パッケージだから安全ではないことでした。.npmrcは地味ですが、依存関係をどれだけ無条件に信じるかを明文化できる数少ない場所です。放置せず、チームのポリシーとして見直しておきたいところですね。

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