npm publish直後にインストールできない原因と対処|マルウェア検査・CI待機・2FA要件

npm publishが成功したのに、直後のnpm installで対象バージョンが見つからない場合、2026年7月28日に導入された公開時のマルウェア検査が原因である可能性があります。

npmでは、新しく公開されたパッケージをすぐにインストール可能にせず、自動スキャンが完了してから利用可能にする仕組みへ変更されました。通常は約5分ですが、混雑状況、パッケージの内容、ファイルサイズによっては15分以上かかる場合があります。したがって、再公開やキャッシュ削除ではなく、CI/CDを「対象バージョンがnpm registryで利用可能になるまで、上限時間を設けて再確認する」設計へ変更するのが基本的な対処です。(The GitHub Blog)

また、ペネトレーションテストツールなど、正当な用途と悪用可能な用途の両方を持つパッケージには、contentPolicyとDISCLOSUREの追加、2FAを強制する公開フローが必要です。通常のパッケージと公開要件が異なるため、CIの修正とあわせて確認しておきましょう。

目次

npm publish後にパッケージが見つからない原因と新しい公開要件

npm publishの完了とインストール可能になる時点が分離された

従来のリリース処理では、npm publishが正常終了すれば、その直後からnpm installできることを前提にしたCI/CDが少なくありませんでした。

新しい仕組みでは、処理が次のように分かれます。

  1. パッケージをnpm registryへ送信する
  2. npm側でマルウェア検査を行う
  3. 検査結果に応じて公開、手動レビュー、ブロックのいずれかになる
  4. 公開されたパッケージがインストール可能になる

つまり、npm publishの正常終了は、直後のインストール可能性まで保証するものではありません。

状態インストール主な挙動
スキャン待ちまだできない通常は数分待機する
スキャン通過可能通常どおり利用できる
手動レビューできない通常より長く待つ可能性がある
ブロックできない通知内容を確認し、必要に応じて異議申し立てを行う

スキャン中でもnpm dist-tagは利用できます。一方、対象バージョンに依存するnpm deprecateやnpm unpublishは、パッケージが利用可能になるまで実行できません。ブロックされた場合は、公開者に通知され、異議申し立ての選択肢が提示されることがあります。(The GitHub Blog)

通常5分でも固定の待機時間にはしない

公式発表では、公開から利用可能になるまでの時間は通常約5分です。ただし、ピーク時間帯やパッケージの内容、サイズによっては15分以上かかる可能性があります。

この時間はサービス保証ではなく、今後のスキャン方式の変更によって変わる可能性もあります。sleep 300のように5分だけ待つ設計では、負荷が高い時間帯に失敗します。(The GitHub Blog)

CI/CDでは、次の方針に変更する必要があります。

  • 固定時間を待つだけにしない
  • パッケージ名とバージョンを明示して確認する
  • 15秒から30秒間隔で再試行する
  • 15分から20分程度の上限時間を設定する
  • 上限を超えたら自動再公開せず、レビューやブロックを確認する

GitHubの公式コミュニティ案内でも、固定時間を前提にするのではなく、ポーリングやリトライで利用可能性を確認するよう案内されています。(GitHub)

最初に行うべき切り分け

パッケージ名、バージョン、公開先を確認する

まず、ローカルのpackage.jsonから、実際に公開したパッケージ名とバージョンを取得します。

PACKAGE_NAME=$(node -p "require('./package.json').name")
PACKAGE_VERSION=$(node -p "require('./package.json').version")

echo "${PACKAGE_NAME}@${PACKAGE_VERSION}"

続いて、CIが参照しているレジストリを確認します。

npm config get registry

npmの公開先は、.npmrc、環境変数、スコープ別設定などで変更できます。公開時はnpm公式レジストリを利用していたのに、確認処理が社内レジストリや別のプロキシを参照していると、スキャンとは無関係に「見つからない」状態になります。

特にスコープ付きパッケージでは、次のような設定がないか確認してください。

@your-scope:registry=https://registry.npmjs.org/

npm publishは設定によって公開先を変更できるため、公開先と確認先を一致させることが重要です。(npm ドキュメント)

exact versionをnpm viewで確認する

latestタグではなく、公開した正確なバージョンを指定して確認します。

npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version \
  --registry="https://registry.npmjs.org/"

利用可能になっていれば、次のようにバージョン番号が返ります。

1.4.2

まだスキャン中の場合は、対象バージョンを取得できません。

npm viewはパッケージや指定バージョンの情報をレジストリから取得するコマンドです。公開直後の待機処理では、npm installを何度も実行するより、軽量な確認手段として扱いやすい方法です。(npm ドキュメント)

同じバージョンを再公開しない

数分待っても見つからないからといって、同じバージョンでnpm publishを再実行するのは避けてください。

npmでは、一度使われたパッケージ名とバージョンの組み合わせは再利用できません。削除した場合でも同じ組み合わせは再利用できないため、スキャン待ちを公開失敗と誤認すると、不要なバージョン更新やリリース履歴の混乱につながります。(npm ドキュメント)

確認する順番は次のとおりです。

  1. 最初のnpm publishが終了コード0で完了したか確認する
  2. パッケージ名とバージョンが意図どおりか確認する
  3. 公開先と確認先のレジストリを一致させる
  4. exact versionを一定時間ポーリングする
  5. タイムアウトした場合はnpmからの通知を確認する
  6. ブロック通知がある場合は、通知内の異議申し立て手順に従う

CI/CDをスキャン待ちに対応させる方法

Bashで利用可能になるまで待機する

LinuxベースのCIでは、次のような待機スクリプトを用意できます。

#!/usr/bin/env bash
set -euo pipefail

PACKAGE_NAME="${PACKAGE_NAME:-$(node -p "require('./package.json').name")}"
PACKAGE_VERSION="${PACKAGE_VERSION:-$(node -p "require('./package.json').version")}"
REGISTRY="${REGISTRY:-https://registry.npmjs.org/}"

# 15秒間隔で80回。最大約20分待機する。
MAX_ATTEMPTS="${MAX_ATTEMPTS:-80}"
INTERVAL_SECONDS="${INTERVAL_SECONDS:-15}"

ERROR_LOG="$(mktemp)"
trap 'rm -f "${ERROR_LOG}"' EXIT

echo "Waiting for ${PACKAGE_NAME}@${PACKAGE_VERSION}"
echo "Registry: ${REGISTRY}"

for ((attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)); do
  if found_version="$(
    npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version \
      --registry="${REGISTRY}" \
      2>"${ERROR_LOG}"
  )" && [[ "${found_version}" == "${PACKAGE_VERSION}" ]]; then
    echo "Available: ${PACKAGE_NAME}@${PACKAGE_VERSION}"
    exit 0
  fi

  # 認証や権限の問題は、スキャン待ちとして再試行しない。
  if grep -Eq 'E401|E403|ENEEDAUTH' "${ERROR_LOG}"; then
    cat "${ERROR_LOG}" >&2
    echo "Authentication or permission error." >&2
    exit 1
  fi

  echo "Not available yet: attempt ${attempt}/${MAX_ATTEMPTS}"

  if (( attempt < MAX_ATTEMPTS )); then
    sleep "${INTERVAL_SECONDS}"
  fi
done

cat "${ERROR_LOG}" >&2 || true
echo "Timed out waiting for ${PACKAGE_NAME}@${PACKAGE_VERSION}" >&2
echo "Do not republish automatically. Check npm notifications and package status." >&2
exit 1

このスクリプトを./scripts/wait-for-npm.shとして保存し、実行権限を付けます。

chmod +x ./scripts/wait-for-npm.sh

GitHub Actionsでは、npm publishの直後に実行します。

- name: Publish package
  run: npm publish

- name: Wait for npm registry availability
  env:
    REGISTRY: https://registry.npmjs.org/
    MAX_ATTEMPTS: "80"
    INTERVAL_SECONDS: "15"
  run: bash ./scripts/wait-for-npm.sh

待機後にクリーンインストールを確認する

npm viewで確認できたら、別の一時ディレクトリで実際にインストールできるか確認すると、リリースの信頼性が高まります。

PACKAGE_NAME=$(node -p "require('./package.json').name")
PACKAGE_VERSION=$(node -p "require('./package.json').version")

TEMP_DIR=$(mktemp -d)
trap 'rm -rf "${TEMP_DIR}"' EXIT

printf '{"private":true}\n' > "${TEMP_DIR}/package.json"

npm install \
  --prefix="${TEMP_DIR}" \
  --no-save \
  --ignore-scripts \
  "${PACKAGE_NAME}@${PACKAGE_VERSION}"

--ignore-scriptsを付けることで、確認時に依存パッケージのライフサイクルスクリプトが実行されるのを防げます。インストールスクリプト自体もテスト対象にする場合は、安全な隔離環境で別途確認してください。

下流の処理は「publish完了」ではなく「利用可能確認後」に開始する

次の処理をnpm publishの直後に開始すると、新しいスキャン方式の影響を受けます。

  • 公開したパッケージを使用するE2Eテスト
  • Dockerイメージのビルド
  • サンプルプロジェクトの更新
  • ドキュメントサイトのコード例検証
  • 依存パッケージの連続公開
  • リリース通知や外部Webhookの送信

リリース処理は、次の順番にします。

npm publish
    ↓
exact versionの利用可能性を確認
    ↓
クリーンインストールテスト
    ↓
依存パッケージの公開
    ↓
リリース通知

「publishコマンドが成功した状態」と「利用者がインストールできる状態」を、別のステータスとして管理することが重要です。

モノレポでは依存順に待機ポイントを入れる

例えば、パッケージBがパッケージAの新バージョンに依存する場合、次のように実行します。

Aをpublish
    ↓
Aのexact versionが利用可能になるまで待機
    ↓
Bをpublish
    ↓
Bのexact versionが利用可能になるまで待機

複数パッケージを連続して一括公開すると、先に公開した依存パッケージがまだスキャン中で、後続のビルドや検証が失敗する可能性があります。

特にChangesets、Lerna、Nxなどを使ったモノレポでは、公開コマンドだけでなく、パッケージ間の利用可能性を確認するゲートを設けてください。

contentPolicyとDISCLOSUREが必要になるパッケージ

dual-useコンテンツとは

npmでは、正当な用途がある一方、悪用にも転用できるセキュリティ関連機能を「dual-use content」としています。

公式ポリシーでは、次のような例が挙げられています。

  • ペネトレーションテストツール
  • セキュリティ研究用ユーティリティ
  • コード難読化ツール
  • 正当な用途と有害な用途の両方を持つ類似ソフトウェア

dual-useパッケージの公開自体は禁止されていません。ただし、通常のパッケージとは異なるスキャンを適用できるように、公開者による明示的な申告が必要です。

申告したからといって自動的に公開が許可されるわけではなく、内容によってはnpmのTrust & Safetyチームによる個別レビューが行われます。悪意あるコードや、主に危害を与える目的で作られたコードは、申告の有無にかかわらず禁止対象です。(npm ドキュメント)

package.jsonにcontentPolicyを追加する

dual-useパッケージでは、package.jsonに次のフィールドを追加します。

{
  "name": "@example/security-tool",
  "version": "1.0.0",
  "contentPolicy": {
    "class": "dual-use"
  }
}

classには、公式に示されているdual-useを指定します。独自の値へ置き換えないでください。(npm ドキュメント)

公開tarballのルートにDISCLOSUREを入れる

package.jsonだけでなく、公開されるtarballのルートにDISCLOSUREという名前のテキストファイルを配置します。

package.json
LICENSE
README.md
DISCLOSURE
dist/

DISCLOSUREには、少なくとも次の内容を記載します。

  • パッケージが提供するdual-use機能
  • 想定している正当な利用目的

例えば、次のように記載できます。

Package: @example/security-tool

Dual-use functionality:
This package can generate and analyze test payloads that may resemble
malicious network traffic.

Intended legitimate use:
The package is intended for authorized security testing, defensive
research, training environments, and validation of security controls.

Users are responsible for obtaining authorization before testing systems
they do not own or operate.

形式は自由ですが、画像やバイナリではなく、テキストだけで構成します。Trust & Safetyチームは、このファイルをdual-useパッケージのレビューに利用する場合があります。(The GitHub Blog)

filesフィールドでDISCLOSUREを除外しない

package.jsonでfilesを指定している場合、リポジトリにDISCLOSUREが存在していても、公開tarballから除外される可能性があります。

次のように明示的に含めてください。

{
  "files": [
    "dist",
    "DISCLOSURE"
  ],
  "contentPolicy": {
    "class": "dual-use"
  }
}

公開前には、次のコマンドでtarballの内容を確認します。

npm pack --dry-run

出力にDISCLOSUREが含まれていることを確認してください。npm pack --dry-runでは、実際に公開されるファイル構成を事前確認できます。(npm ドキュメント)

一度申告したdual-use情報は次のバージョンでも必要

一度dual-useメタデータを付けて公開したパッケージは、後続バージョンでもcontentPolicyとDISCLOSUREを維持する必要があります。

新しいバージョンで片方を削除すると、公開が拒否される可能性があります。dual-use申告を取り下げるには、npmのTrust & Safetyチームによるレビューが必要です。(npm ドキュメント)

2FAが必須になる条件と公開方法

通常のパッケージに適用される2FA要件

npmの現行ドキュメントでは、パッケージの作成と公開には、次のいずれかが必要です。

  1. npmアカウントで2FAを有効にし、対話的に公開する
  2. bypass 2FAを有効にしたGranular Access Tokenを使用する
  3. CI/CDからTrusted PublishingのOIDC認証を利用する

パッケージ設定の変更にも2FAが必要です。

パッケージごとのPublishing accessでは、次の2種類を選択できます。

設定公開方法
2FAまたはbypass 2FAトークンを要求対話的2FAと対象トークンを許可
2FAを要求しトークンを禁止対話的公開のみ。公式推奨設定

Trusted Publishingは長期間有効なトークンを保存せず、CI実行時に短期間の認証情報を発行する方式です。通常のCI/CDでは、トークンよりTrusted Publishingが推奨されています。(npm ドキュメント)

dual-useパッケージにはより厳しい公開制限がある

dual-useパッケージでは、すべての公開で2FAを強制する必要があります。

公開方法dual-useでの利用
ローカルから対話的に2FA付きで直接公開利用可能
bypass 2FAトークンで直接公開利用不可
Trusted PublishingのOIDCで直接公開利用不可
OIDCでstagingし、人が2FAでapprove利用可能
bypass 2FAトークンでstagingし、人が2FAでapprove利用可能
ローカルセッションでstagingし、人が2FAでapprove利用可能

GitHub Changelogの概要ではTrusted Publishingが2FAを強制する公開方法の例として挙げられていますが、npmの詳細なDual-Use Content Policyでは、dual-useパッケージのOIDCによる直接公開を明確に認めていません。

実際のCI/CDは詳細ポリシーに合わせ、OIDCを使う場合もnpm publishではなくnpm stage publishを使用し、その後に人が2FAで承認する構成にするのが安全です。(The GitHub Blog)

新規dual-useパッケージは対話的な2FA公開が必要

Staged Publishingには、対象パッケージがすでにnpm registryに存在している必要があります。新しいパッケージの初回公開には利用できません。

そのため、新規dual-useパッケージの初回公開は、次の流れになります。

  1. contentPolicyを追加する
  2. DISCLOSUREをtarballのルートに含める
  3. npm pack --dry-runで内容を確認する
  4. 2FAを有効にしたローカルセッションからnpm publishする
  5. 利用可能になるまでポーリングする

2回目以降は、CIからstagingできます。

npm stage publish

その後、メンテナーがステージIDを確認します。

npm stage list @example/security-tool
npm stage view <stage-id>

内容を確認したうえで、2FAを使って承認します。

npm stage approve <stage-id>

npm stage publish自体には2FAが不要ですが、公開レジストリへ昇格させるnpm stage approveでは2FAが要求されます。

Staged Publishingには、npm CLI 11.15.0以降とNode.js 22.14.0以降が必要です。(npm ドキュメント)

bypass 2FAトークンへの依存は早めに解消する

2026年7月31日の変更により、bypass 2FAを設定したGranular Access Tokenでは、次の操作ができなくなりました。

  • トークンの作成と削除
  • パッケージのアクセス設定変更
  • メンテナーの変更
  • Trusted Publishing設定の変更
  • OrganizationやTeamのメンバー管理
  • パッケージ権限の付与

さらにnpmは、2027年1月を目標に、bypass 2FAトークンによる直接公開も廃止する予定です。その後は、主にプライベートパッケージの読み取りと、2FA承認を前提としたstagingに用途が限定される予定です。(The GitHub Blog)

通常パッケージの自動公開はTrusted Publishingへ移行し、dual-useパッケージはTrusted PublishingとStaged Publishingを組み合わせるのが、今後の変更にも対応しやすい構成です。

よくある失敗と対処

症状主な原因対処
npm publish成功直後に対象バージョンを取得できないマルウェアスキャン中exact versionを上限付きでポーリングする
5分待ってもインストールできない混雑、サイズ、追加スキャン15分以上かかる前提で待機上限を延ばす
npm dist-tagは成功するがnpm unpublishは失敗するスキャン中の操作制限利用可能になるまで待つ
ローカルでは見えるがCIでは見えないregistry設定の不一致.npmrc、scope、npm config get registryを確認する
private packageのnpm viewがE401になる読み取り認証がない読み取り専用トークンを確認処理に渡す
latestでは取得できないがexact versionでは取得できるdist-tagが意図と異なるnpm dist-tag ls <package>で確認する
dual-useパッケージをOIDCで直接公開できない詳細ポリシー上の制限npm stage publishと2FA承認へ変更する
DISCLOSUREを置いたのに拒否される公開tarballに含まれていないfilesへ追加し、npm pack --dry-runで確認する
長時間待っても利用できず通知が来た手動レビューまたはブロック自動再公開せず、通知内の手順を確認する

Trusted PublishingのOIDC認証は、基本的に公開操作のためのものです。private packageに対するnpm viewやnpm installなどには、別途読み取り権限を持つトークンが必要になる場合があります。(npm ドキュメント)

npm publish直後にインストールできない場合の対応まとめ

新しいマルウェア検査への対応で重要なのは、npm publishの成功をリリース完了と判断しないことです。

まずCI/CDに、正確なパッケージ名とバージョンを使ったポーリング処理を追加してください。通常約5分でも、15分以上かかる可能性を考慮し、20分程度の上限を設けると運用しやすくなります。

依存関係のある複数パッケージを公開する場合は、先行パッケージの利用可能性を確認してから後続パッケージを公開します。タイムアウト時は新しいバージョンを自動生成せず、npmからの通知、レジストリ設定、認証エラーを確認してください。

セキュリティ研究ツールなどのdual-useパッケージでは、次の対応も必要です。

  • package.jsonへcontentPolicy.class = "dual-use"を追加する
  • 公開tarballのルートへテキスト形式のDISCLOSUREを追加する
  • 後続バージョンでも申告を維持する
  • 初回公開は対話的な2FA付きで行う
  • CI公開はstagingと人による2FA承認を組み合わせる
  • OIDCによる直接公開やbypass 2FAトークンによる直接公開を避ける

最初に修正すべきなのは、リリースパイプラインの「公開完了」の判定条件です。npm publishの終了ではなく、exact versionの取得とクリーンインストールが成功した時点を、正式なリリース完了として扱いましょう。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次