Transcript
00:00:00NPMパッケージをインストールするなら、あなたは標的です。今日ではないかもしれない、今週ではないかもしれないが、
00:00:05確実にその時は来ます。ここ数ヶ月だけでも数百ものパッケージが侵害されており、
00:00:09その勢いは衰えていません。毎回不安に思う代わりに、
00:00:14私は実際にNPM、PNPM、BUN全体で自分の設定をロックダウンしました。その結果、
00:00:18身を守るための設定のほとんどは、30秒で終わることが分かりました。この動画では、
00:00:23私が実際に行った7つの変更点を紹介します。最も一般的な攻撃を即座に阻止する
00:00:27たった1行の設定変更から、攻撃者が投稿した悪意あるパッケージであっても、
00:00:30マシンに到達する前に検知してくれる無料ツールまで解説します。では始めましょう。
00:00:39最初にして最も単純なのは、新しいパッケージのダウンロードを控えることです。こうしたサプライチェーン攻撃の
00:00:44ほとんどは数時間以内に検知されます。ですから、新しいバージョンが公開された瞬間に
00:00:48インストールしなければ、こうしたサプライチェーン攻撃のほとんどを完全に回避できます。主要なNode.js
00:00:52パッケージマネージャーは現在、リリース経過時間による制限機能を備えています。NPMの場合は、.npmrc
00:00:57ファイルで日数単位で設定します。PNPMの場合は、pnpm-config.yamlファイルでグローバルに設定するか、
00:01:03pnpm-workspace.yamlファイルを使ってプロジェクト単位で設定でき、この値は分単位で指定します。
00:01:08また、PNPM 11以降ではデフォルトで1日に設定されているため、
00:01:14設定しなくてもある程度の保護が得られます。BUNの場合は、bunfig.tomlファイルで、
00:01:17グローバルまたはプロジェクト単位で設定し、こちらは秒単位で指定します。
00:01:233つのパッケージマネージャーで同じ設定なのに単位がバラバラなのは本当に驚きで、
00:01:27このエコシステムの現状をよく表していると思います。設定が完了すれば、
00:01:32例えばTanStackのreact-startなどをインストールしようとしても、リリース経過期間の条件を満たしていない
00:01:36最新バージョンはインストールされません。私の場合、その基準は1週間前です。
00:01:41もしこれをバイパスする必要がある場合、例えばインストールしようとしているパッケージにセキュリティ上の問題があり、
00:01:45どうしても最新版を入れる必要があるならコマンドラインから可能ですが、
00:01:49LLMを使う際にも注意してください。LLMが勝手にこの制限をバイパスして最新版をインストールしてしまうケースを私は見てきました。
00:01:54また、npxやbunxはこの設定を尊重せず、
00:01:59常に最新バージョンをインストールしてしまう点にも注意してください。
00:02:03Bunにはこれを修正するプルリクエストが開かれています。設定の話のついでに、
00:02:07NPMのインストールスクリプトも無効化しておきましょう。PNPMとBunはデフォルトでこの動作になっているので、
00:02:12特別な設定は不要です。ご存知ないかもしれませんが、パッケージをインストールすると、
00:02:16そのパッケージはインストール直後に独自のコードを実行することを許可されます。これは、
00:02:20ネイティブコードのコンパイルやバイナリのダウンロードのような正当なユースケースのために行われてきましたが、
00:02:26問題は、ほぼすべてのサプライチェーン攻撃が、インストール直後にマシンで悪意のあるコードを実行するためにこの手法を使っていることです。
00:02:30どうしてもスクリプトが必要なパッケージがある場合は、明示的に許可することができます。
00:02:34PNPMの場合、ポストインストールスクリプトを持つパッケージをインストールしようとすると、
00:02:39esbuildのように通知が出るので、”pnpm approve-builds”を実行してどれを許可するかを選択できます。
00:02:44これにより、pnpm-workspaceのallow-builds設定が更新されます。インストールコマンドに
00:02:48allow-buildフラグを付けることでも同様の設定が可能です。Bunの場合、前述の通りデフォルトで
00:02:52インストールスクリプトは停止されますが、Bunにはスクリプトの実行を許可されたパッケージの
00:02:56厳選リストがあり、それには私がインストールしたesbuildなども含まれます。
00:03:00package.jsonに”trustedDependencies”を追加することで、このリストを制御できます。
00:03:04こうすることで、明示的に許可したパッケージのみがポストインストールスクリプトを実行できるようになります。
00:03:09配列を空に設定すればデフォルトのリストを上書きできると書かれていますが、
00:03:12私の環境では動作せず、GitHubでバグとして報告されているようです。
00:03:17現在の回避策は、リストに何か1つ適当な値を入れることで、デフォルトのリストを無視させることです。
00:03:21Bunでは”bun pm untrusted”を実行して、スクリプト実行を求めているのにまだ信頼されていないパッケージを確認し、
00:03:26“bun pm trust”で許可するか、前述のtrustedDependenciesに追加できます。
00:03:30また、globalのbunfig.tomlで”install-scripts = false”と設定し、スクリプトを完全に無効化することも可能です。
00:03:35NPMの場合、これは少し難しいです。正直なところ、NPMは使わずにPNPMを使うべきですが、
00:03:40どうしてもNPMを使う必要があるなら、Lavamoteの”allow-scripts”パッケージを使えば
00:03:45似たような許可リストによる制御が可能です。これでpackage.jsonで管理できます。
00:03:503つ目のヒントは、Gitベースの依存関係をブロックすることです。NPMでは、依存関係を
00:03:55GitのURLとして宣言でき、レジストリをバイパスできます。さらに、ライフサイクルスクリプトを再有効化する独自の.npmrcを同梱することも可能です。
00:04:01これは最近TanStackを襲ったNPMサプライチェーン攻撃で使用された手口の一つです。
00:04:05NPM設定で”allow-git = none”に設定すれば完全にブロックできます。
00:04:10もう一つの選択肢は”root”に設定することです。これならGitベースの依存関係は許可されますが、
00:04:15ルートのpackage.jsonで宣言されているものに限られるため、自分で明示的に設定したもののみとなります。
00:04:19PNPMの場合、”block-exotic-sub-dependencies”という設定があります。これをtrueにすると、
00:04:25ルートのpackage.jsonにリストされた直接的な依存関係のみが、GitリポジトリやダイレクトURLといった
00:04:29エキゾチックなソースを利用できます。このオプションはBunにはまだありませんが、追加のPRが出ています。
00:04:35設定変更に関するヒントの最後に、PNPMには”trust-policy”という設定があり、
00:04:40これを”no-downgrade”に設定できます。これは、パッケージの信頼レベルが
00:04:45以前のリリースと比べて低下した場合、PNPMが失敗するようにするものです。もし以前の公開者が
00:04:50信頼されていたのに、今はプロヴナンス(出所証明)しかない、あるいは
00:04:55信頼の証拠がないパッケージになった場合、インストールは失敗します。これにより、
00:05:00公開プロセスを悪用しようとする攻撃を防げるはずです。ヒント4は、おそらく最も強力なものです。
00:05:04インストールするパッケージをマシンに到達させる前に監査してくれるツールを使うことです。
00:05:08そのために、2つの強力で完全に無料のツールがあります。1つ目は
00:05:14“mpq”です。普段のnpm、pnpm、bunコマンドにエイリアスを作成して設定します。
00:05:18インストールを実行するたびに、いくつかチェックを行います。Snykのデータベースと照合して
00:05:23既知の脆弱性をスキャンし、22日未満のパッケージにフラグを立てます。また、
00:05:28タイポスクワッティングも検知します。これは例えば”express”のスペルを”expres”のように間違えて
00:05:32悪意あるパッケージをインストールさせる攻撃です。さらにレジストリ署名や
00:05:37出所証明(プロヴナンス)を検証し、インストール前後のスクリプトが存在すれば警告を出します。
00:05:41さらに、ダウンロード数、READMEの有無、ライセンス、リポジトリURL、
00:05:46メンテナーのメールアドレス、そのドメインが登録されているかまで確認します。これらすべてを行った上で、
00:05:51インストールするかどうかを判断できる対話型のレポートを出力します。
00:05:552つ目は”Socket Firewall”です。私が実際に使っているのがこれです。
00:05:59普段のパッケージマネージャーにエイリアスを設定して使用します。こちらは
00:06:03JavaScript以外にも、PythonやRustもサポートしています。MPQと同様に、
00:06:08インストール時にSocketがチェックを行い、人間が確認した悪意あるパッケージをブロックし、
00:06:12未レビューのAI検知による脅威についても警告を出します。正直なところ、
00:06:16Socket Firewallを選んだのは最初に知ったからですが、Socket社は多くの
00:06:21悪意あるパッケージを検知しており、攻撃者自身がインタビューで
00:06:25Socketはパッケージがマシンに届く前にマルウェアを検知すると語っているほどなので、かなり信頼しています。
00:06:30JavaScriptだけでなく、uv、pip、cargoなどもサポートしている点も気に入っています。
00:06:35これらをインストールしたら、すべてのパッケージが確実にファイアウォールで
00:06:38チェックされるよう、パッケージマネージャーのキャッシュをクリアしておきましょう。ヒント5は
00:06:42ロックファイルについてです。ロックファイルにはすべてのパッケージの実際のダウンロードURLが記述されていますが、
00:06:47問題は、PRでロックファイルをレビューする人がほとんどいないことです。誰かが
00:06:51あなたのレポジトリにPRを出せば、解決URLを密かに自分が管理するパッケージに変更し、
00:06:56一致する整合性ハッシュ値を設定して、何も問題ないように見せかけることができます。すると、
00:07:01次のインストールで攻撃者のサーバーからコードを取得してしまいます。朗報としては、PNPMを使っていれば
00:07:05これには脆弱ではありません。書き換え可能なソース情報を保持せず、
00:07:09package.jsonで宣言されていないものはインストールしないからです。Bunについては確実な情報が
00:07:14見つからなかったので、まだ脆弱かもしれません。PNPMを使っていない場合の解決策は、
00:07:18“lockfile-lint”というツールを使うことです。これを開発依存としてインストールして
00:07:23ロックファイルを検証し、すべてのパッケージがNPMレジストリのような信頼されたホストから解決されているかチェックします。
00:07:28解決されたURLがパッケージ名と一致しているか、整合性ハッシュが本物かを確認します。
00:07:32改ざんの疑いがあれば、インストールを失敗させます。ヒント6は、CIや本番環境で
00:07:37npm installを使うのをやめ、クリーンインストールを使うことです。NPMでのコマンドは
00:07:42“npm ci”です。BunやPNPMの場合は”--frozen-lockfile”フラグを追加しますが、同様の
00:07:47CI用エイリアスコマンドもあります。PNPMはCI環境だと自動でこれを使います。
00:07:52クリーンインストールコマンドは、ロックファイルに記述されたものだけをインストールします。
00:07:57もしロックファイルとpackage.jsonが一致しなければ、勝手に推測してインストールすることなく
00:08:01停止してエラーを投げます。これにより、攻撃者がこっそり差し替えた
00:08:05バージョンを混入させるのを防げるはずです。もちろん、ロックファイルをコミットしていなければ
00:08:09何の意味もありません。gitignoreに入れず必ずバージョン管理下に含めましょう。子供の頃に
00:08:13プログラミングを始めた当初、私はこれらを無視すべきだと思っていました。コミットする重要性を学んで良かったです。
00:08:17ここまで6つのヒントは設定とツールに関するものでしたが、習慣によって
00:08:21回避できる攻撃もあります。1つ目は、むやみにアップデートしないことです。
00:08:25“npm update”などのコマンドを適当に実行して、すべての依存関係を一気に
00:08:30最新版にするのは、まさに攻撃者が望んでいる行為です。なぜそのアップグレードが必要なのか
00:08:35自問自答してからアップグレードするようにしましょう。2つ目の習慣は、
00:08:39使うパッケージを減らすことです。依存関係を追加するたびに攻撃対象が増えます。
00:08:43攻撃の多くは、依存関係のさらに先の依存関係を通じて広がります。なぜその
00:08:48ライブラリが必要なのかを考える価値は十分にあります。よくある例としては、
00:08:53小さなコードで代用できるLodashや、Fetchで十分なのにAxiosを使っているケースです。
00:08:58エージェントがコーディングする時代になり、依存関係を使う代わりに
00:09:03AIに数行の関数を書かせるのは簡単になっています。
00:09:083つ目の習慣は、依存関係を正確なバージョンにピン留めすることです。これにより、
00:09:12アップグレードが常に意図的な選択になります。ただし、これはpackage.jsonで指定したパッケージのみをロックし、
00:09:17依存関係の依存関係はまだ独自の範囲指定を使えるため、ヒント1の「経過期間による冷却期間」が
00:09:21重要なのです。これらを組み合わせれば、侵害されたパッケージを
00:09:25誤ってインストールすることはないという安心感が得られるでしょう。無敵ではありませんが、
00:09:29何もしないよりはずっとマシです。究極のヒントは、すべてを硬化されたDevコンテナで実行することですが、
00:09:34少し面倒に感じるので、結局ローカル開発に戻ってしまいます。もし他にも
00:09:38こうした攻撃から自分を守る方法を知っていれば、下のコメント欄で教えてください。
00:09:42チャンネル登録もお願いします。また次回の動画でお会いしましょう。
Community Posts
No posts yet. Be the first to write about this video!
Write about this video