エクスポートした Rork プロジェクトの node_modules を、何気なく数えてみたことがあります。画面が四枚ほどの小さなアプリでした。それでも、インストールされていたパッケージはおよそ 900 を超えていました。自分が名前を知っていたのは、そのうち十数個だけです。
直接の依頼は「写真を選んで保存するだけ」の機能でした。Rork はその実現に必要なライブラリを選び、それぞれが内部で頼る別のライブラリを連れてきて、最終的に 900 という数字になっていたわけです。これは Rork が悪いのではなく、現代の JavaScript 開発では当たり前の光景です。問題は、その当たり前を個人開発で何本も抱えたときに起きます。
私は壁紙や癒し系のアプリを複数本、並行して運用しています。それぞれが数百の間接依存を抱えていると、ある日どこかのライブラリに脆弱性が見つかったとき、自分のどのアプリが影響を受けるのかが、一目では分からなくなります。AI が依存を選んでくれるからこそ、選ばれた中身を人間が棚卸しする習慣が要ります。
以下では、Rork が生成したアプリの依存関係を、供給網のリスクを増やさずに保つための監査手順を、実際に流しているコマンドとあわせて書いていきます。
まず「自分が何に頼っているか」を可視化する
棚卸しの第一歩は、直接依存と間接依存を分けて眺めることです。package.json に並ぶのは直接依存だけで、本当のリスクはその下にぶら下がる間接依存に潜みます。小さなアプリでも、間接依存が全体の 90% 近くを占めることは珍しくありません。
直接依存の一覧は、次のコマンドで深さを一段に絞って確認します。
# 直接依存だけを一覧表示(間接依存は畳む)
npm ls --depth=0
# 間接依存まで含めた総数を数える
npm ls --all --parseable | wc -l
# 特定のライブラリが「なぜ」入っているのかを逆引きする
npm why <パッケージ名>
最後の npm why が特に効きます。見覚えのないパッケージを見つけたとき、それがどの直接依存から連れてこられたのかを遡れます。「この画像ライブラリのために、古い圧縮ライブラリが芋づる式に入っていた」といった発見は、ここから生まれます。
脆弱性は週に一度、機械的に洗う
可視化の次は、既知の脆弱性の確認です。これは判断を挟まず、毎週きまった曜日に機械的に流すのが私のやり方です。
# 既知の脆弱性を深刻度つきで一覧
npm audit
# 破壊的変更を伴わない範囲だけ自動修正
npm audit fix
# 深刻度が high 以上だけを CI で弾く(戻り値で判定)
npm audit --audit-level=high
注意したいのは、npm audit fix を勢いで流さないことです。--force をつけると、互換性を壊すメジャー更新まで一気に当たり、本番環境のビルドが通らなくなる落とし穴があります。私は high 以上だけを対象に、一つずつ更新の影響を確かめてから取り込みます。
複数アプリを持っていると、この週次の洗い出しを一本のスクリプトにまとめておくと楽です。
#!/usr/bin/env bash
# audit-all.sh — 全アプリの脆弱性をまとめて確認する
set -e
for app in ~/apps/*/; do
echo "===== $(basename "$app") ====="
( cd "$app" && npm audit --audit-level=high || echo "要対応の脆弱性あり" )
done
これを週末に一度回すだけで、どのアプリに手当てが必要かが一覧で分かります。脆弱性への対処を「思い出したとき」から「毎週の点検」へ移すだけで、見落としは大きく減ります。
バージョンを固定して、再現性を守る
AI が生成したプロジェクトでありがちなのが、package.json のバージョン指定が緩いことです。^ や ~ がついたままだと、別の日に npm install したときに、間接依存が静かに別バージョンへ動きます。昨日まで動いていたビルドが、コードを一行も変えていないのに今日は通らない——その多くは、この緩さが原因です。
再現性を守る要は lockfile です。package-lock.json を必ずコミットし、CI では install ではなく ci を使います。
# lockfile に厳密に従ってインストール(再現性優先)
npm ci
そのうえで、間接依存の特定バージョンに既知の問題があると分かったときは、overrides で狙い撃ちに固定します。
{
"overrides": {
"問題のあるライブラリ": "1.2.4"
}
}
overrides は、直接依存していないライブラリのバージョンを上書きできる仕組みです。脆弱性のある間接依存を、親ライブラリの更新を待たずに先回りで差し替えられます。
使っていない依存を、定期的に削る
監査でもう一つ見落とされがちな落とし穴が、もう使っていないのに残っている依存です。Rork に何度も再生成を頼むうちに、過去の実装で使ったライブラリが package.json に残ることがあります。
# 参照されていない依存を検出する
npx depcheck
depcheck が「未使用」と判定したものは、本当に消してよいかをコード検索で一度確かめてから外します。使われていない依存は、攻撃面を無駄に広げるだけでなく、脆弱性アラートのノイズにもなります。
ネイティブ SDK は別枠で管理する
JavaScript の依存だけを見ていると、見落とす領域があります。広告(AdMob)や課金まわりのネイティブ SDK です。これらは npm の依存ツリーとは別の経路で入り、App Store のプライバシーマニフェストにも影響します。
私はネイティブ SDK については、バージョンと用途、そしてプライバシー上の申告内容を、JavaScript 依存とは別の一覧で管理しています。npm audit はここを見てくれないため、SDK 提供元のリリースノートを手で追う必要があります。広告 SDK の更新は eCPM や審査要件に直結するため、ここだけは機械任せにせず、変更点を自分の目で確認することを推奨します。
監査を習慣に落とし込む
最後に、ここまでの手順を続けられる形にまとめます。私は次の頻度で回しています。
- 週に一度: 全アプリへ npm audit --audit-level=high をスクリプトで一括実行
- 新機能を Rork に頼んだ直後: npm ls --depth=0 と npm why で増えた依存を確認
- 月に一度: depcheck で未使用依存を棚卸し、lockfile を再生成
頻度を決めてしまえば、依存監査は「思い立ったときの大掃除」から「淡々とした点検」へ変わります。AI が依存を選ぶ時代だからこそ、選ばれた結果を定期的に見直す側の役割が、個人開発者には残り続けると感じています。
複数アプリで依存を揃えると、監査が一度で済む
個人開発で何本もアプリを持つと、それぞれが少しずつ違うバージョンの同じライブラリを抱えがちです。あるアプリは画像ライブラリの 5 系、別のアプリは 6 系、というふうにずれていくと、脆弱性が一つ出るたびに、全アプリを別々に調べ直すことになります。
私はこれを避けるため、よく使う土台のライブラリについては、アプリをまたいでバージョンをそろえる方針にしています。React Native と Expo の SDK、画像処理、状態管理あたりは、特別な理由がない限り全アプリで同じ系に固定します。
# 全アプリで特定ライブラリのバージョンを横並びに確認する
for app in ~/apps/*/; do
printf "%-20s " "$(basename "$app")"
( cd "$app" && npm ls expo --depth=0 2>/dev/null | grep expo@ )
done
この一覧でバージョンがそろっていれば、脆弱性が出たときの調査は一度で済みます。そろっていなければ、次の更新のタイミングで少しずつ寄せていきます。土台をそろえる作業は手間に見えますが、アプリが増えるほど、監査一回あたりのコストを下げてくれます。AdMob などのネイティブ SDK についても、同じ考え方でバージョンを横並びにしておくと、App Store の審査対応をまとめて進められます。
次の一歩
手元の Rork アプリで、まず npm ls --all --parseable | wc -l を流してみてください。その数字が、あなたが暗黙に信頼している供給網の広さです。数を知ることが、棚卸しの最初の一歩になります。