#依存パッケージという入口
いまのアプリは、自分で書いたコードより、他人が書いたパッケージのほうが多くなりがちです。npm install を実行すると、自分で指定したパッケージだけでなく、そのパッケージが使う別のパッケージも、芋づる式に入ってきます。攻撃者は、その連なりのどこか 1 か所に入り込めば、それを使う人すべてに届きます。入り込み方は、おもに次の 3 つです。
| 手口 | 起きること |
|---|---|
| 乗っ取り | 正規のパッケージの公開権限を奪い、悪意ある版を公開する |
| 偽物 | 有名なパッケージに似せた名前(expresss など)で、偽のパッケージを公開する |
| 仕込み | 正規に見えるパッケージに、インストール時に動くコードを入れる |
実際にあった例です。2021 年 10 月、npm の人気パッケージ ua-parser-js の 3 つの版(0.7.29、0.8.0、1.0.0)で、悪意のあるコードが公開されました。修正版は 0.7.30、0.8.1、1.0.1 です(GitHub Advisory)。使う側の操作に誤りがあったわけではありません。いつもどおりに更新しただけで、入り込むおそれがありました。
npm のパッケージは、preinstall、install、postinstall、prepare などの「ライフサイクルスクリプト」を持てます。これは、npm install を実行した人の権限で、インストールの途中に自動で実行されます。アプリを起動する前、コードを一行も読まないうちに、実行が終わります。
#インストール時に動くコードを一覧する
RoamSwitch の インストール時に動くコードの点検 は、node_modules の中にあるパッケージのライフサイクルスクリプトを、一覧にして見せます。何も実行しません。
無害なダミーを作ります。demo-helper はインストール時に動くスクリプトを持ち、plain-lib は持ちません。スクリプトが指す setup.js は存在しないので、実行されることもありません。
mkdir -p app/node_modules/demo-helper app/node_modules/plain-lib
echo '{ "name": "demo-helper", "version": "1.0.0", "scripts": { "postinstall": "node setup.js" } }' > app/node_modules/demo-helper/package.json
echo '{ "name": "plain-lib", "version": "2.0.0" }' > app/node_modules/plain-lib/package.json
# 確認済みの記録は ~/.config/roamswitch/ に保存されます。演習では、使い捨てのホームフォルダを使います
LAB_HOME=$(mktemp -d)
HOME=$LAB_HOME roamswitch scan-scripts ./appこれは脅威判定ではなく一覧表示です。何も実行しません。⚠️は誤検知を含む軽量ヒューリスティックです。
demo-helper 1.0.0 [postinstall]
node setup.js
確認済みの状態がまだありません。今のインストールスクリプトを確認したら、roamswitch scan-scripts --accept を実行してください。次回からは、増えた分だけを知らせます。スクリプトを持つ demo-helper だけが出て、持たない plain-lib は出ません。判定ではなく一覧なので、悪意があるかどうかは、人が見て判断します。
一覧を確認したら、その状態を確認済みとして記録します。次の更新で、スクリプトを持つパッケージが増えたときだけ、知らせが出ます。
HOME=$LAB_HOME roamswitch scan-scripts --accept ./app
# 更新で、新しいパッケージが入ったと仮定します
mkdir -p app/node_modules/new-helper
echo '{ "name": "new-helper", "version": "1.0.0", "scripts": { "install": "node build.js" } }' > app/node_modules/new-helper/package.json
HOME=$LAB_HOME roamswitch scan-scripts ./app確認済みの状態を記録しました。次回からは、増えた分だけを知らせます。
(…一覧…)
確認済みにした日(2026-11-03)の後で、インストール時に動くコードが増えたパッケージが 1 件あります。
● new-helper - → 1.0.0 新しいパッケージがインストール時のコードを持っています (install)数百のパッケージがあるのに、毎回一覧を読み直すのは現実的ではありません。「前回から増えたか」だけを見るようにすると、ua-parser-js のように、いつもどおりの更新に紛れた変化に気づきやすくなります。
#名前の似たパッケージを見つける
偽物は、有名なパッケージの名前に似せて作られます。打ち間違いを狙うので、typosquatting(綴り違いを狙う攻撃)と呼ばれます。名前の似たパッケージの判定 は、package.json に書かれた依存パッケージの名前を、有名な名前と比べます。
cat > app/package.json <<'EOF'
{ "name": "demo-app", "version": "1.0.0",
"dependencies": { "express": "^4.0.0", "expresss": "^4.0.0", "reakt": "^18.0.0", "lodahs": "^4.17.0" } }
EOF
roamswitch scan-typosquat ./appこれは静的な参考情報であり断定ではありません。ネットワーク接続は一切行いません。正規の類似パッケージが誤検知されることもあります。
⚠️ expresss は次に似ています: "express" (distance 1) [./app]
⚠️ reakt は次に似ています: "react" (distance 1) [./app]expresss と reakt は見つかりました。express は正規の名前なので、出ません。一方、lodahs(lodash の綴りを入れ替えたもの)は出ていません。
ほかの綴り違いも、実機で試しました。
| 名前 | 本物との違い | 見つかったか |
|---|---|---|
expresss | 1 文字の追加(express) | 見つかった |
reakt | 1 文字の置き換え(react) | 見つかった |
recat | 隣り合う 2 文字の入れ替え(react) | 見つからない |
axois | 隣り合う 2 文字の入れ替え(axios) | 見つからない |
loadsh | 隣り合う 2 文字の入れ替え(lodash) | 見つからない |
lodahs | 隣り合う 2 文字の入れ替え(lodash) | 見つからない |
試した範囲では、1 文字の追加・置き換えは見つかり、隣り合う文字の入れ替えは見つかりませんでした。結果は、製品の版や、受け取った名前の一覧によって変わることがあります。「見つからなかった」ことは、安全の証明にはなりません。
#守りを組み立てる
RoamSwitch の一覧と検知は、対策の一部です。次のような対策を組み合わせます。
- インストールを制御する:
npm ciでロックファイルのとおりに入れます。スクリプトを実行したくないときは--ignore-scriptsを付け、必要なパッケージにだけ、スクリプトを許します - 新しい版を、すぐには使わない: 公開された直後の版は使わず、数日待ちます。乗っ取られた版は、気づかれて取り下げられるまでの間が、いちばん危険です
- スクリプトを閉じ込める: RoamSwitch の
roamswitch installは、コマンドの説明によると、npmやpnpmのinstallを包んで、スクリプトを隔離した環境の中だけで動かします(この教材では、実行して確かめていません) - 増えた分を見る: 更新のたびに、「スクリプトを持つパッケージが増えたか」を確かめます
- 一覧に出たからといって、すべてを悪意のあるものと決めつける。ビルドに必要な正規のスクリプトも、たくさんあります
- 一覧に出ないからといって、安全だと決めつける(下の「見えない部分」を読んでください)
#見えない部分
- スクリプトの中身は、読みません。一覧に出るのは、スクリプトがあるという事実と、そのコマンドだけです。悪意があるかどうかは、判定しません
- スクリプトを使わない攻撃は、一覧に出ません。パッケージを読み込んだとき(
requireしたとき)に動くコードは、ライフサイクルスクリプトではないので、対象外です - 名前の比較で見つかるのは、1 文字の追加と置き換えです(実機で確かめた範囲)。隣り合う文字の入れ替えは、見つかりませんでした
- 比べる相手は、有名なパッケージの名前の一覧です。その一覧にない名前に似せた偽物は、比べようがありません
更新後に scan-scripts を実行しても、増えたパッケージは何も表示されませんでした。確実に言えるのはどれですか
解説を見る
見ているのは、「インストール時に動くコードを持つパッケージが増えたか」だけです。読み込み時に動くコードや、すでにスクリプトを持つパッケージの中身が変わったことは、この点検では分かりません。
#次に学ぶこと
- 依存パッケージにある既知の脆弱性は、サーバー運用者の危機対応 で、修正版がない場合も含めて学べます
- 依存パッケージ経由で秘密情報が盗まれる経路への備えには、認証情報の盗難と、秘密情報の漏えい が役に立ちます
対応する攻撃手法 (ATT&CK): T1195.001
2026-11-03に確認しました。対象は Linux 1.17.30 です。Ubuntu 26.04 の Server Edition で、無害なダミーの node_modules に対して実行
コマンドの出力は、製品の版や環境によって変わります。違いが出たら、まず製品の版を確かめてください。