概要
(GitHub等が親切に"We found potential security vulnerabilities in your dependencies."のように通知してくれるので便利)
対応フロー
ざっくり全体像は以下のとおり。
①最新のコードを取得する
いうまでもないが、手元のコードは最新のコードにしておく
git pull
最新であることが確認された
Already up to date.
ちなみに、今回は自作プロジェクト(https://github.com/riversun/simple-date-format) で実際にハンズオンした
②プロジェクトが使用しているnpmパッケージが最新かどうか確認する
脆弱性対応の前に、いま自分のプロジェクトが使っているnpmパッケージを最新のものにする。
プロジェクトが使っているnpmパッケージが最新かどうかはnpm outdated
コマンドで確認することができ、最新バージョンがある場合は、その情報を表示してくれる
npm outdated
するとこのように、最新版が存在するパッケージを一覧表示してくれる
いくつかのパッケージで最新版があるようだ
パッケージをアップデートする
package.jsonに記載されるnpmパッケージ一覧は以下のようになっている。
(「^」がついたキャレット表記についてはこちらで説明)
"@babel/core": "^7.8.4",
"@babel/preset-env": "^7.8.4",
"babel-jest": "^25.1.0",
...
パッケージのバージョンの付け方セマンティックバージョニングに従っている。
セマンティックバージョニングにおいて、メジャーバージョン、マイナーバージョンは以下の意味をもっている。
これをふまえ、「パッケージを最新にする」には2種類の方法があるといえる。(パッチバージョンを上げるというのもいれれば3種類だが本筋にあまり影響ないので割愛)
- ②-1 マイナーバージョンまで最新にする
- ②-2 メジャーバージョンまで最新にする
②-1をとるか ②-2をとるかはポリシー次第だがメジャーバージョンを最新にする場合はAPIの後方互換が無いことを想定しておいたほうがいい。
②-1 マイナーバージョンまで最新にする場合
キャレット表記「^」つきで定義されたパッケージのバージョンを、マイナーバージョンまでを最新にするには**npm update**コマンドで可能
npm update
以下のように、マイナーバージョンまでが最新になった
(ただし、メジャーバージョンは最新になっていない。)
+ jest@25.2.7
+ babel-loader@8.1.0
+ cross-env@7.0.2
+ @babel/core@7.9.0
+ babel-jest@25.2.6
+ @babel/preset-env@7.9.0
+ webpack@4.42.1
added 36 packages from 17 contributors, removed 6 packages, updated 140 packages, moved 1 package and audited 263490 packages in 18.399s
②-2 メジャーバージョンまで最新にする場合
npm update
では、マイナーバージョンまでしかアップデートしてくれなかったが、ここではメジャーバージョンまで容赦なくアップデートしてくれるパッケージ**npm-check-updates**を導入する
以下のようにしてnpm-check-updatesをインストールする
npm install -g npm-check-updates
npm-check-updatesをインストールするとncu
というコマンドが使えるようになる。
ちなみに、ncuコマンドだけをたたくと現在のパッケージバージョンと最新のパッケージバージョンを表示してくれるnpm outdatedコマンドのような動作をする
ncu
を実行すると、以下のように現在のバージョンと最新バージョンが表示される。
つづいて、ncu -uを実行すれば、上でみたとおりパッケージが最新バージョンになるようにpackage.jsonを書き換えてくれる
ncu -u
npm updateと違ってパッケージのインストールそのものはやってくれない。package.jsonが書き換わるだけなので、自分で npm install する必要がある
npm install
これでパッケージは最新になった
③npm auditで脆弱性のある依存パッケージを確認する
今、最新のパッケージにした状態だが、この状態でも脆弱性(valnerability)のあるパッケージが含まれていることがある。
**npm audit**コマンドを使えば脆弱性のあるパッケージを洗い出すことができる。
npm audit
をやってみたら、180個の脆弱性がみつかった。レベルはlow。
found 180 low severity vulnerabilities in 263397 scanned packages
run `npm audit fix` to fix 174 of them.
6 vulnerabilities require manual review. See the full report for details.
④npm audit fixで自動修復をこころみる
npm audit fixをすると、脆弱性のあるパッケージのバージョンを自動的に脆弱性の無いバージョンに置き換えてくれる(努力をしてくれる)
npm audit fix
すると以下のようになった。
180個の脆弱性のうち174個が修正された。
removed 1 package and updated 2 packages in 4.641s
31 packages are looking for funding
run `npm fund` for details
fixed 174 of 180 vulnerabilities in 263397 scanned packages
6 vulnerabilities required manual review and could not be updated
残り6個はマニュアルレビューしてくれとかいてある。
⑤npm dedupeで重複したパッケージの整理統合をこころみる
npm dedupeを理解する
まず、dedupeの概念を理解するために以下の状態を考える
- 自プロジェクトがnpmパッケージAとnpmパッケージBに依存している
- npmパッケージAはnpmパッケージC @2.1.1に依存している。
- npmパッケージBはnpmパッケージC @2.2.0に依存している。
この場合、npmを使っていると1npmパッケージC @2.1.1もnpmパッケージC @2.2.0もインストールされる。
パッケージとしてはnpmパッケージCで同じなのに、npmパッケージC @2.1.1とnpmパッケージC @2.2.0でバージョンが違いがあるので両方保持されしまう。
だったら、バージョンを新しいほうの@2.2.0のほうに合わせて、npmパッケージC @2.2.0のほうを、npmパッケージAとnpmパッケージBで共通化して使いましょう、というのがdedupeの発想。
npm dedupeは新しいバージョンをインストールしてくれない
npm update等でdedupeはひととおりのパッケージを最新にした後にやる。
なぜなら、dedupe自身はパッケージの重複排除などはやってくれるが、最新のパッケージを入れてくれるわけではないので古いパッケージバージョンで状態でdedupeしてもあまり意味がない。
dedupeする
さて、ではnpm dedupe (npm ddpでもOK)してみる
removed 10 packages and audited 263298 packages in 3.622s
found 0 vulnerabilities
脆弱性が0になった!
なぜdedupeで脆弱性がゼロになった?
これは、dedupeが脆弱性を排除しているというより、古いパッケージバージョンに依存していたライブラリが新しいパッケージバージョンを参照するようにdedupeが変更してくれた効果のため。
上の例でいうと以下のようになる。
- 自プロジェクトがnpmパッケージAとnpmパッケージBに依存している
- npmパッケージAはnpmパッケージC @2.1.1(脆弱性ありバージョン)に依存している。
- npmパッケージBはnpmパッケージC @2.2.0**(安全バージョン)**に依存している。
の状況をdedupeが↓のように変更してくれた効果
- 自プロジェクトがnpmパッケージAとnpmパッケージBに依存している
- npmパッケージAはnpmパッケージC @2.2.0**(安全バージョン)**に依存している。
- npmパッケージBはnpmパッケージC @2.2.0**(安全バージョン)**に依存している。
⑦回帰テストを実行する
さてここまでで、脆弱性の無い状態にできたら、最後にテストをしてパッケージバージョンを変更したことによるプロジェクトのデグレが発生していないかどうかを確認する。
npm test
テスト無事通過!
(といっても、今回はdevDependenciesの依存のみだったけど)
これで対応完了!
途中でつまずいた場合
上述のコマンドだけでうまくいかず、つまずくことも多々ある。そうした場合は、コマンドだけで楽に突破できない状況になっている可能性があるので、それなりの調査分析工数を覚悟するしかない。
つまずき例
-
vulnerabilityが無くならなかった
-
対応案:
npm audit
でvulnerabilityのあるパッケージに依存している上位のパッケージを特定する。そのパッケージがdeprecateになっていないか。ちゃんとメンテされているか確認する。deprecatedだったりメンテされていなければ使うのをやめたりPR送ってみたり、自分で直してみたり、代替パッケージを探したり。そういった工数を覚悟、確保する。 -
最後のテストでつまづいた
-
対応案:手順を1手ずつロールバック(手順を戻す)しながら、都度
npm test
を実行し、どの段階でつまづいたのか特定する。ありがちなのは②-1メジャーアップデートでAPIに破壊的変更
まとめ
-
こういった関連サービスもあります
-
yarnは自動的にdedupeしてくれる(https://classic.yarnpkg.com/ja/docs/cli/dedupe/) ↩