タイトルが長い。しかもパッとわからない。
ので以下のストーリーを理解されたい。
ストーリー
GitHubでセキュリティアラートをONにしていると依存しているライブラリに脆弱性があったときにこんな感じに教えてくれる。
そしてDependabotをONにしていると基本的に自動で脆弱性に対応するためのPRを作成してくれる。
ただなんでかたまにPRを作ってくれないときがあって自分でなんとかしないといけない時があり、そういうときは自分で依存モジュールを上げないといけない。
まあライブラリが対応さえしてくれていれば上げるだけでOKなのだが。
だがしかし
難しいのは脆弱性があるdependencyが直接依存しているモジュールじゃないとき。
例えば C
というライブラリのバージョン"1.0.8"に脆弱性があるとする。
しかし自分のプロジェクトのpackage.jsonに C
は書かれておらず直接の依存はしていなくて C
は 直接依存している A
というライブラリが利用している B
というライブラリの中でさらに依存しているというケース。
言葉で書くとわからん。
図にするとこう
/node_modules ├── A │ └── node_modules │ └── B │ └── node_modules │ └── C ├── react ├── dateFns └── ...
Aの孫にCがある
ここで問題が出る
素直に考えれば直接依存している A
が脆弱性対応してくれるのを待つのだが、全世界すべてのライブラリが活発にメンテされてるわけでもないし、メンテされていてもすぐに対応されるとは限らない。
たとえばこういう風に A
が対応してくれなかったとする
/node_modules ├── A < ぼくはBの"3.3.7"に依存しているけど、Bを"3.3.8"に更新対応するつもりはない │ └── node_modules │ └── B < "3.3.7"でCの"1.0.8"を使ってたけど、"3.3.8"でCの"1.0.9"に上げたので脆弱性は対応済みです │ └── node_modules │ └── C < "1.0.8"で脆弱性あったけど、"1.0.9"で対応済みです ├── react ├── dateFns └── ...
😇 詰んでしまった……。
どうすればよいか
Dependabotが脆弱性対応のPRを投げてくれるときのPRを思い出してみる。
そういえばlockファイルをなんか書き換えてくれていて対応版のバージョンを指定している。
Dependabotと同じようにlockファイルをなんとかして手動で更新できれば上述でいうライブラリ A
が対応してくれなくても C
は脆弱性対応してくれているのでなんとかなりそう。
つまり自分がDependabotと同じことをできればOK。俺たちはDependabotになりたい。
手順
やっとこさ本題。
yarnでは resolutions
というフィールドに書かれたバージョンで強制的に利用するバージョンを固定するという技ができるので、それを用いる。
https://classic.yarnpkg.com/ja/docs/selective-version-resolutions/
ここからは例として "lodash": "4.17.15"
に脆弱性があり "4.17.19"
で 対応されたとする。先ほどからのツリー構造でみるとこう。
/node_modules ├── A │ └── node_modules │ └── B │ └── node_modules │ └── lodash // "4.17.15"を使っていて脆弱性がある ├── react ├── dateFns └── ...
上述してきたライブラリ C
= lodash
ということ。
1 package.jsonに問題のあるパッケージを書く
dependencies or devDependencies と resolutions に書く。
{ "name": "example-project", "version": "1.0.0", "dependencies": { "lodash": "4.17.19" // もともとこのプロジェクトでlodashは直接使ってないが一旦入れる }, "resolutions": { "lodash": "4.17.19" // このバージョンで固定する } }
node_modules内に複数のメジャーバージョンがある場合は
resolutionsにパスとglobを書けるので複数のバージョン指定をそれぞれに対して行えばOK
Selective dependency resolutions | Yarn
"resolutions": { "d2/left-pad": "1.1.1", "c/**/left-pad": "^1.1.2" }
2 lockファイルを更新
そしてインストールする。
$ yarn install
するとこんな感じのyarn.lockのlodashの欄にDiffが出る。
. . . integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= lodash@^4.11.1, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4: version "4.17.19" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== loose-envify@^1.0.0, loose-envify@^1.4.0: version "1.4.0" . . .
Dependabotがやってくれるやつだ!
3 package.jsonを元に戻す
あとはさっき1で書いたpackage.json
の内容を消す。
{ "name": "example-project", "version": "1.0.0", "dependencies": { }, "resolutions": { } }
※ 実際にはこんな感じにまっさらではなく他のdepsは書かれている状況
なんで書いたものを消すかというと、あくまでlockファイルを更新するために書いたに過ぎないから。直接プロジェクトの依存として使っているわけではないし、resolutionsに書いたままにしておくと、あとになってなんでresolutionsに書かれて固定されているのかわからなくなる。
しかもresolutionsに書いてあると永遠に固定されるので、今回の例だと今後 "lodash": "4.17.19"
以降のバージョンが出ても永遠に "4.17.19"
を利用しようとするからよくない。なのでpackage.jsonから記述は消してしまって、lockファイルだけを更新しておく。
以上、自分がDependabotになるにはどうすればよいかという話。長かったね。
Dependabotがやってくれないとか待ってられないときは自分たちがDependabotと同じことをするしかないので覚えておいて損はない気がする。
こういうのなかなか知見が書かれにくいし表現しにくいので書いた。