npmとnpxでの最新パッケージ汚染予防策についてPATH操作とpnpmを併用してやってみた
最近、npmやnpxで最新のパッケージをインストールする際に懸念されるのが、マルウェアShai-huludの感染です。
対策として、pnpmでリリースから一定期間経過したパッケージのみをインストールし、rcファイル内へaliasでnpmとnpxを無効化する方法があります。ただし、スクリプト内にnpmやnpxが記述されている場合はaliasが効かないために防ぐことができません。
今回は、スクリプト実行時にnpmやnpxが呼ばれるケースにも対処できないか検討し、プロジェクトルートの.envrcを使ったPATH操作を試してみました。
設定手順
誤動作を考慮して、各プロジェクト内に設定する形となります。
- プロジェクト内に
.binディレクトリを作成 - npm/npxのラッパーを配置
- direnvを使用してPATHの先頭に
.binを追加
以下のスクリプトを実行します。
#!/bin/bash
#
# npm/npxコマンドをpnpm/pnpxにリダイレクトするセットアップスクリプト
#
# このスクリプトは以下の処理を実行します:
# 1. .binディレクトリの作成
# 2. npmシムスクリプトの作成
# 3. npxシムスクリプトの作成
# 4. 実行権限の付与
# 5. .envrcの更新
# 6. direnvの再読み込み
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
BIN_DIR="${SCRIPT_DIR}/.bin"
ENVRC_FILE="${SCRIPT_DIR}/.envrc"
echo "========================================="
echo "npm/npxをpnpm/pnpxにリダイレクトするセットアップ"
echo "========================================="
echo ""
# 1. .binディレクトリの作成
echo "ステップ 1/6: .binディレクトリを作成中..."
mkdir -p "${BIN_DIR}"
echo "✓ .binディレクトリを作成しました: ${BIN_DIR}"
echo ""
# 2. npmシムスクリプトの作成
echo "ステップ 2/6: npmシムスクリプトを作成中..."
cat > "${BIN_DIR}/npm" << 'EOF'
#!/bin/bash
echo "⚠️ npm detected, redirecting to pnpm" >&2
exec pnpm "$@"
EOF
echo "✓ npmシムスクリプトを作成しました: ${BIN_DIR}/npm"
echo ""
# 3. npxシムスクリプトの作成
echo "ステップ 3/6: npxシムスクリプトを作成中..."
cat > "${BIN_DIR}/npx" << 'EOF'
#!/bin/bash
echo "⚠️ npx detected, redirecting to pnpx" >&2
exec pnpx "$@"
EOF
echo "✓ npxシムスクリプトを作成しました: ${BIN_DIR}/npx"
echo ""
# 4. 実行権限の付与
echo "ステップ 4/6: 実行権限を付与中..."
chmod +x "${BIN_DIR}/npm" "${BIN_DIR}/npx"
echo "✓ 実行権限を付与しました"
echo ""
# 5. .envrcの更新
echo "ステップ 5/6: .envrcを更新中..."
if [ ! -f "${ENVRC_FILE}" ]; then
# .envrcが存在しない場合は新規作成
cat > "${ENVRC_FILE}" << 'EOF'
# npm/npxコマンドをpnpm/pnpxにリダイレクト
PATH_add .bin
EOF
echo "✓ .envrcを新規作成しました"
elif ! grep -q "PATH_add .bin" "${ENVRC_FILE}"; then
# .envrcが存在するが PATH_add .bin がない場合は先頭に追加
TMP_FILE=$(mktemp)
cat > "${TMP_FILE}" << 'EOF'
# npm/npxコマンドをpnpm/pnpxにリダイレクト
PATH_add .bin
EOF
cat "${ENVRC_FILE}" >> "${TMP_FILE}"
mv "${TMP_FILE}" "${ENVRC_FILE}"
echo "✓ .envrcにPATH_add .binを追加しました"
else
echo "✓ .envrcには既にPATH_add .binが設定されています"
fi
echo ""
# 6. direnvの再読み込み
echo "ステップ 6/6: direnvを再読み込み中..."
if command -v direnv &> /dev/null; then
# direnvがインストールされている場合
direnv allow "${SCRIPT_DIR}"
echo "✓ direnvを再読み込みしました"
echo ""
echo "========================================="
echo "セットアップが完了しました!"
echo "========================================="
echo ""
echo "動作確認:"
echo " which npm"
echo " npm --version"
echo ""
else
echo "⚠️ direnvがインストールされていません"
echo ""
echo "========================================="
echo "セットアップは完了しましたが、direnvのインストールが必要です"
echo "========================================="
echo ""
echo "direnvをインストールしてください:"
echo ""
echo " # macOS (Homebrew)"
echo " brew install direnv"
echo ""
echo " # シェル設定ファイルに追加"
echo " echo 'eval \"\$(direnv hook bash)\"' >> ~/.bashrc # bashの場合"
echo " echo 'eval \"\$(direnv hook zsh)\"' >> ~/.zshrc # zshの場合"
echo ""
echo "インストール後、以下のコマンドを実行してください:"
echo " direnv allow"
echo ""
fi
手作業も検討しましたが、ClaudeCodeに依頼したところすぐに作成してくれました。
設定後のnpm/npx実行結果
まずはnpmでの実行結果を確認します。pnpmにリダイレクトされ、導入が完了しています。
$ npm install neovim
⚠️ npm detected, redirecting to pnpm
dependencies:
+ neovim ^5.4.0
Packages: +30
++++++++++++++++++++++++++++++
Progress: resolved 30, reused 0, downloaded 30, added 30, done
Done in 1.5s using pnpm v10.23.0
次にnpxの場合。pnpm dlxを実行してエラーとなっていますが、今回対象としたneovimはライブラリであり、コマンドラインツールではないため問題ありません。
$ npx install neovim
⚠️ npx detected, redirecting to pnpx
Packages: +1
+
Progress: resolved 1, reused 0, downloaded 1, added 1, done
ERR_PNPM_DLX_NO_BIN No binaries found in install
最後に、スクリプト経由でインストールする場合。
$ cat npm_demo.sh
npm install neovim
$ sh npm_demo.sh
⚠️ npm detected, redirecting to pnpm
Already up to date
Progress: resolved 30, reused 0, downloaded 0, added 0, done
Done in 208ms using pnpm v10.23.0
pnpmへのリダイレクトが正常に動作しました。
あとがき
今回の対策については、Geminiと只管壁打ちを行った結果となります。preinstallにてonly-allowを用いる方法等も提示されましたが、パッケージ指定でのインストールには効果を成しません。
今回提案した方法は、記事内のセットアップスクリプトを実行せずとも手作業で十分に実現可能です。プロジェクト内でnpmとnpxをコマンドとして残しつつ、pnpmで代替できる環境を手軽に構築したい場合の選択肢の一つとしておすすめします。







