npm アカウントおよび GitHub アカウントの両方で多要素認証を有効化する。
ロックファイル(package-lock.json / pnpm-lock.yaml)を Git にコミットし、CI では frozen install を使用する。
| パッケージマネージャ | コマンド / 挙動 |
|---|---|
| npm | npm ci(ロックファイルの内容を厳密に再現する) |
| pnpm | CI 環境を検出すると --frozen-lockfile が自動付与される |
効果:npm install / pnpm install が semver 範囲内で最新版を解決する動作を抑止し、ロックファイルに記録されたバージョンのみをインストールする。
Renovate の minimumReleaseAge プリセットを設定する。
{
"extends": ["security:minimumReleaseAgeNpm"]
}npm パッケージの公開から 3 日間は更新 PR を生成しない。悪意あるバージョンが公開後に発覚・取り下げされるまでの時間的余裕を確保する。
ロックファイル固定は install 時の解決を制御し、minimumReleaseAge はロックファイル更新 PR の生成タイミングを制御する。対象フェーズが異なるため併用する。
npm は preinstall / postinstall 等のライフサイクルスクリプトをインストール時に実行する。これを無効化すると、悪意あるパッケージが混入した場合でも任意コード実行を防止できる。
プロジェクトルートの .npmrc に追加:
ignore-scripts=true影響範囲:主要なパッケージ(sharp, esbuild, swc, rollup, lightningcss 等)はプラットフォーム別プリビルドバイナリを optionalDependencies で配布する方式に移行済みであり、postinstall を使用しないため ignore-scripts=true の影響を受けない。
node-gyp によるビルドが必要なパッケージ(bcrypt 等)は影響を受ける。純粋な JS 実装の代替(bcrypt → bcryptjs)に置き換え可能な場合はそちらを採用する。
どうしても postinstall が必要なパッケージが残る場合、pnpm では package.json に許可リストを記述できる:
{
"pnpm": {
"onlyBuiltDependencies": ["some-native-package"]
}
}npm にはこの許可リスト機構がないため、ignore-scripts=true を維持したうえで対象パッケージのみ npm rebuild <package> を手動実行する形になる。
.env 等のファイルにクラウドアクセスキーを記載しない。1Password CLI や Bitwarden CLI を使用し、プロセス起動時に環境変数として注入する。
# 1Password CLI の例
op run --env-file=.env.tpl -- npm start効果:ファイルシステム経由のシークレット窃取を不可能にする。
注意:ここでの脅威は .env の Git コミットではなく、.gitignore に含まれていてもディスク上にファイルとして存在すること自体である。postinstall 等で実行された悪意あるコードはユーザー権限でファイルシステムを読めるため、ローカルの .env は読み取り可能になる。ランタイム注入方式ではシークレットがファイルとして存在しないため、この経路での窃取が成立しない。
タグ指定(@v5 等)はタグの参照先コミットを第三者が変更できるため、意図しないコードが実行される可能性がある。pinact を使用し、ワークフローファイル内のアクション指定をコミットハッシュに変換する。
go install github.com/suzuki-shunsuke/pinact/cmd/pinact@latest
pinact run変換前:
uses: actions/checkout@v5変換後:
uses: actions/checkout@<full-commit-hash> # v5GitHub のリポジトリ設定で、コミットハッシュ指定以外の Actions 実行を禁止する。pinact による変換漏れがあった場合にワークフロー実行自体が失敗するため、二重の防御になる。
- npm / GitHub アカウントの MFA 有効化
- ロックファイルの Git コミット
- CI での
npm ciまたは frozen install の使用 - Renovate
minimumReleaseAgeの設定 -
.npmrcへのignore-scripts=true追加 - シークレットのランタイム注入化(
.envへのキー直接記載の廃止) - GitHub Actions アクションのコミットハッシュ固定(pinact)
- GitHub リポジトリ設定でのハッシュ指定強制