12k
All articles

npm に意図しないファイルを公開しないための方法

npmで意図しないファイルや秘密情報を公開しない方法を解説。.npmignore、.gitignore、package.jsonのfilesの関係を理解し、npm pack --dry-runで確認できます。

OpenReplay Team
OpenReplay Team
npm に意図しないファイルを公開しないための方法

パッケージに .npmignore ファイルがあると、npm は tarball から除外するファイルを判断する際に .gitignore を読まなくなります。これは .npmignore ファイルが空の場合でも同じです。

多くの人がこの挙動に気づくのは、tarball のサイズが想定よりはるかに大きくなったときや、公開するつもりのなかったドットファイルがレジストリに上がってしまったときです。ignore ルール自体には問題がなく、実際に正しく機能していました。2 つ目の ignore ファイルが追加されるまでは。

本記事では、意図しないファイルがパッケージに含まれてしまう落とし穴を解説します。具体的には、.gitignore へのフォールバック、files と .npmignore の相互作用、npm に組み込まれた固定の包含・除外リスト、そして問題のあるバージョンをすでに公開してしまった場合の対処法です。なお、How to Create and Publish an npm Package を読んでいることを前提としています。

重要なポイント

  • .npmignore ファイルが存在すると、たとえ空であっても、本来 tarball に適用されるはずの .gitignore ルールがすべて無効になります。
  • npm はすべてのサブディレクトリにある .npmignore と .gitignore を読み込むため、ディレクトリ階層の深い場所にある ignore ファイルが公開内容を変えてしまうことがあります。
  • npm の組み込み除外リストには、.env ファイル、秘密鍵、証明書は含まれていません。
  • シークレットがレジストリに公開されてしまった場合は、まずそれをローテーションしてください。unpublish しても、すでにダウンロードされたコピーは回収できません。
  • Provenance は、どのリポジトリ、コミット、ワークフローによってそのバージョンがビルドされたかを記録するものです。コードの安全性を証明するものではありません。

npmignore の罠:ファイルを 1 つ追加しただけで gitignore のルールがすべて無効になる

npm がパッケージから除外するファイルを判断する際に使うのは、.npmignore か .gitignore のどちらか一方であり、両方が使われることはありません。npm のパッケージからファイルを除外するためのルールは簡潔です。.npmignore がなければ、npm は .gitignore を適用します。.npmignore が 1 つでも存在すれば、.gitignore は考慮されなくなります。2 つのファイルがマージされることはありません。

よくあるきっかけはビルド成果物です。dist/ は gitignore されているため、npm は tarball からそれを除外します。そこで dist/ を含めるために .npmignore を追加します。すると dist/ は公開されるようになりますが、それまで .gitignore によって隠されていた .env.local、coverage/、test/fixtures/ もすべて公開されてしまいます。

npm が参照するのはルートフォルダだけではありません。パッケージ内のあらゆるフォルダから両方の種類の ignore ファイルを読み込むため、ネストされたフォルダに紛れ込んだ .npmignore によって、誰もルートの設定に手を触れていないのに公開内容が変わってしまうことがあります。

変更前と変更後:ファイルリストの変化

以下のスクリプトは、.gitignore がすべてのフィルタリングを担うパッケージを作成します。

mkdir pkg-trap && cd pkg-trap
npm init -y >/dev/null
mkdir -p src dist test/fixtures coverage
echo 'module.exports = 1' > src/index.js
echo 'module.exports = 1' > dist/index.js
echo 'API_KEY=replace-me' > .env.local
echo '{}' > test/fixtures/big.json
echo 'x' > coverage/lcov.info
printf 'dist/\n.env.local\ncoverage/\ntest/fixtures/\n' > .gitignore

npm pack --dry-run

npm は次のように出力します(パスのみ、一部省略)。

package.json
src/index.js

dist/ が含まれていないため、ビルド成果物を公開してソースを除外する 1 行だけの .npmignore を追加します。

echo 'src/' > .npmignore
npm pack --dry-run
  package.json
- src/index.js
+ dist/index.js
+ .env.local
+ coverage/lcov.info
+ test/fixtures/big.json

修正方法は、引き続き必要なルールをすべて .npmignore にコピーすることです。

printf 'src/\n.env*\ncoverage/\ntest/\n*.map\n' > .npmignore

files と .npmignore:許可リストか拒否リストか

package.json の files フィールドは許可リスト(allow-list)で、.npmignore は拒否リスト(deny-list)です。両方を併用すると、パッケージに何が含まれるかを 1 つのファイルだけでは把握できなくなります。

仕組みモデル存在を忘れていた新しいファイル
files許可リスト含まれない
.npmignore拒否リスト公開される
.gitignore(フォールバック)拒否リスト公開される(gitignore されていない限り)

問題が起きるのは、files と .npmignore が重複する部分です。package.json のドキュメントによると、パッケージのルートにある .npmignore は、files で追加されたものを除外できません。しかし、サブフォルダ内の .npmignore は除外できます。

{ "name": "my-lib", "version": "1.0.0", "main": "dist/index.js", "files": ["dist"] }
# dist/.npmignore
*.map

この例では、files が dist/ を取り込み、ネストされた ignore ファイルがそこからソースマップを除外しています。つまり、1 つのリストを 2 つのファイルで定義している状態です。独自に出力フォルダを書き出すビルドツールを使うと状況はさらに悪化するため、pnpm のガイドに従っている場合は、dist/ に何が出力されているかを確認してください。最もシンプルなルールは、どちらか一方の仕組みを選び、もう一方を削除することです。

npm が常に含めるファイルと常に除外するファイル

一部のファイルは、設定にかかわらず常に含まれるか、常に除外されます。package.json のドキュメントによると、npm は package.json、README、LICENSE または LICENCE、main で指定されたファイル、bin で指定されたファイルを常に含めます。一方、バージョン管理用のフォルダ、.npmrc、node_modules、エディタが生成する不要なファイルは常に除外されます。シークレットはこのリストに含まれていません。

常に含まれる常に除外される(一部)明示しない限り除外されない
package.json、README(およびその派生)、LICENSE / LICENCE、main のファイル、bin のファイル.git、.npmrc、node_modules(バンドルされた依存関係を除く)、.DS_Store、npm-debug.log、config.gypi、.*.swp、*.orig、package-lock.json.env、.env.local、*.pem、*.key、フィクスチャ、カバレッジ

右端の列に対するガードを追加しましょう。

npm pack --dry-run 2>&1 | grep -E '\.env|\.pem$|\.key$' && { echo "STOP: secrets in tarball"; exit 1; }

公開前には必ず npm pack —dry-run を実行する

npm pack --dry-run は、tarball を書き出すことなく、npm が公開するファイルの正確なリストを出力します。そのため、ここまでに紹介したすべての罠を検出できます。ignore ファイル、files フィールド、ビルド設定のいずれかを変更したら実行し、CI では npm publish の前に組み込んでください。基本的なワークフローについては How to Create and Publish an npm Package で解説しています。

npm pack --dry-run

意図しないファイルをすでに公開してしまった場合の対処法

シークレットがレジストリに公開されてしまった場合は、まずそれをローテーションし、今後は永久に公開済みのものとして扱ってください。バージョンを unpublish しても、すでにダウンロードまたはミラーされたコピーは削除されません。そのうえで、レジストリをクリーンアップします。

  1. 認証情報の提供元で、その認証情報をローテーションします。
  2. 問題のあるバージョンを削除するか、フラグを付けます。npm の unpublish ポリシーでは、公開から 72 時間以内であれば、他の公開パッケージが依存していない限り unpublish できます。この期間を過ぎると、ポリシーの基準を満たすパッケージのみが unpublish 可能です。それ以外の場合は npm deprecate を使用します。
  3. クリーンなパッチバージョンを公開します。一度使用したバージョン番号は、unpublish した後であっても二度と再利用できません。
# Within 72 hours, subject to npm's unpublish policy
npm unpublish my-lib@1.4.2

# Otherwise
npm deprecate my-lib@1.4.2 "Contains a leaked file. Upgrade to 1.4.3."

# Fix the ignore rules, confirm, republish
npm pack --dry-run
npm version patch
npm publish

Provenance が証明するのは出所であり、安全性ではない

npm の provenance ステートメントは、どのリポジトリ、コミット、CI ワークフローによってパッケージのバージョンがビルドされたかを記録します。そのコミット内のコードが安全であることや、tarball に意図したファイルだけが含まれていることを示すものではありません。GitHub Actions では、ワークフローに id-token: write が必要です。

name: publish
on:
  release:
    types: [published]
permissions:
  contents: read
  id-token: write
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
          registry-url: https://registry.npmjs.org
      - run: npm ci
      - run: npm pack --dry-run
      - run: npm publish

このワークフローは、trusted publishing を設定済みであることを前提としています。trusted publishing には npm CLI 11.5.1 以降と Node 22.14.0 以降が必要で、provenance を自動的に生成するため、ジョブに --provenance も NODE_AUTH_TOKEN も必要ありません。代わりにトークンを使って公開する場合、npm publish --provenance には npm 9.5.0 以降が必要です。いずれの場合も、package.json の repository フィールドは公開元のリポジトリと完全に一致している必要があり(大文字と小文字は区別されます)、リポジトリはパブリックでなければなりません。npm ci が peer 依存関係の競合で失敗する場合は、How to Fix npm ERESOLVE Errors を参照してください。2FA と trusted publishing の設定については、npm Security Best Practices を参照してください。

まとめ

npm の ignore ルールは、エラーを出して失敗することはありません。ファイルを 1 つ追加しただけで、公開されるファイルが気づかないうちに変わってしまいます。files か .npmignore のどちらか一方を選んでもう一方を削除し、サブディレクトリには ignore ファイルを置かないようにしましょう。そして CI では、すべての npm publish の前に、シークレットを検出する grep と組み合わせた npm pack --dry-run を実行してください。

よくある質問

npm pack --dry-run と npm publish --dry-run の違いは何ですか?

どちらも何もアップロードせずに tarball に含まれるファイルを報告するため、ignore ファイルのミスはどちらでも検出できます。ファイルの内容を確認するには、npm pack --dry-run の方がシンプルです。npm publish --dry-run は publish コマンドそのもののリハーサルを行います。現在の npm バージョンでは、そのバージョンがすでに存在するかをレジストリに問い合わせ、存在する場合は失敗します。ただし、ログインしていなくても警告が出るだけです。ログインと 2FA が実際に検証されるのはアップロード時のみであるため、dry-run が成功してもアップロードが成功するとは限りません。

すでに公開されたバージョンに含まれるファイルを確認するにはどうすればよいですか?

レジストリのスペックを指定して npm pack を実行します。たとえば npm pack my-lib@1.4.2 のように実行すると、npm は公開済みの tarball を取得し、カレントディレクトリに my-lib-1.4.2.tgz として保存したうえで、ファイル名を出力します。内容を一覧表示するには tar -tzf my-lib-1.4.2.tgz を実行してください。curl などのツールで使う tarball の URL を取得するには、npm view my-lib@1.4.2 dist.tarball を実行します。

npm deprecate を使えば、漏洩したファイルを含むバージョンのダウンロードを防げますか?

いいえ。npm deprecate はレジストリのエントリに警告メッセージを追加し、そのバージョンをインストールするユーザーに npm がそのメッセージを表示しますが、tarball は引き続きダウンロード可能です。正確なバージョンを固定しているユーザーは、漏洩したファイルを依然として取得します。レジストリからバージョンを削除できるのは unpublish だけであり、それも npm の unpublish ポリシーによって制限されます。後から deprecation を解除するには、メッセージに空文字列を指定して npm deprecate を再度実行します。

package.json の files フィールドで glob パターンは使えますか?

はい、使えます。単純なファイル名やフォルダ、または * や **/* などの glob パターンを指定でき、フォルダを指定するとその中身がすべて取り込まれます。構文は .gitignore を逆にしたもので、パターンに一致したファイルは除外されるのではなく tarball に追加されます。package.json、README、LICENSE、main のファイルなど、npm が常に含めるファイルは、配列に記載されているかどうかにかかわらず公開されます。

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.