nodeプロジェクトで新規 dependency 追加時に、自動で脆弱性チェックを行う

# 概要
npm (yarn) によるパッケージ管理は非常に楽ですが、ときには脆弱性を含むパッケージを入れてしまうこともあるかもしれません。
CI/CD パイプラインで 脆弱性チェックを行うのも良いかもしれませんが、ローカルで依存パッケージを追加・コミットする前に脆弱性チェックを行い、無駄なコミットが含まれないようにするほうがスマートだと私は思っています。

そこで、 `lint-staged`と`husky`を使って、依存関係を追加した場合のcommitで脆弱性チェック（`audit`）をする設定をします。

# 関連パッケージ等
- yarn （npmでも脆弱性チェックはできますが、ここではyarnを使います）
- lint-staged
- husky

# やったこと（結論）
`husky`と`lint-staged`をdevDependenciesに入れて、`package.json` に以下を追加しましょう。

```package.json
{
  "scripts": {
    "audit:moderate": "yarn audit --level moderate || EXIT_CODE=$? && if [ $EXIT_CODE -ge 4 ]; then exit EXIT_CODE; else exit 0; fi;"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "yarn.lock": [
      "audit:moderate"
    ]
  },
}
```

# 設定の詳細
1. 脆弱性チェック
2. 脆弱性チェックを、中レベル以上に限定する
3. コミット時に脆弱性チェックが走るようにする
4. 中レベル以上の脆弱性チェックが正常終了するようにする

## 1. 脆弱性チェック
yarn には 脆弱性チェックを行う `audit` というコマンドがあるので、それを使います。

詳細は公式ドキュメント：https://classic.yarnpkg.com/en/docs/cli/audit/#toc-yarn-audit

```
$ yarn audit
```

もし脆弱性のあるパッケージがある場合、下記の表のような形式で脆弱性のあるパッケージが出力されます。

```
$ yarn audit
yarn audit v1.22.5
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ low           │ Denial of Service                                            │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ node-fetch                                                   │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in    │ >=2.6.1 <3.0.0-beta.1|| >= 3.0.0-beta.9                      │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ next                                                         │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ next > @ampproject/toolbox-optimizer >                       │
│               │ @ampproject/toolbox-core > cross-fetch > node-fetch          │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://www.npmjs.com/advisories/1556                        │
└───────────────┴──────────────────────────────────────────────────────────────┘

...
```

## 2. 脆弱性チェックを、中レベル以上に限定する
すべてのレベルの脆弱性を排除することが望ましいですが、依存パッケージのさらに依存パッケージの低レベル脆弱性まで気にすると、実運用する上では使えないパッケージが増えてしまいます。
例えば、React開発で最も優れている（と私が思う） `Next.js`でも、上記のように`node-fetch`などのパッケージの低レベル脆弱性が対応しきれていません。

そこで、中程度（`moderate`）以上の脆弱性のみを検知したいと思います。

```
$ yarn audit --level moderate
yarn audit v1.22.5
5 vulnerabilities found - Packages audited: 2235
Severity: 5 Low
✨  Done in 2.02s.
```

低レベル脆弱性が検知されているものの、レベルを`moderate`にしているのでコマンドは脆弱性のレポートを表示することなく終了しています。

## 3. コミット時に脆弱性チェックが走るようにする
`lint-staged`と`husky`を使って、依存パッケージに変更があるコミットで自動で脆弱性チェックが走るようにします。

- [lint-staged](https://github.com/okonet/lint-staged)
- [husky](https://github.com/typicode/husky)

これらの設定は主に`eslint`や`prettier`と組み合わせて使われており有名かと思うので割愛します。

依存パッケージに変更がある場合、`yarn.lock`が更新されているので、`yarn.lock`のコミットをトリガーに脆弱性チェックを行います。

```package.json
{
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged",
    }
  },
  "lint-staged": {
    "yarn.lock": [
      "yarn audit --level moderate"
    ]
  },
}
```

## 4. 中レベル以上の脆弱性チェックが正常終了するようにする

前述の脆弱性チェックコマンドは一見正常終了に見えますが、実はエラー終了扱いとなっています。

試しに`precommit`を走らせると、下記のように。

```
$ git commit yarn.lock
husky > pre-commit (node v12.18.0)
✔ Preparing...
⚠ Running tasks...
  ↓ No staged files match *.{js,json,md,yaml,yml} [SKIPPED]
  ↓ No staged files match *.{ts,tsx} [SKIPPED]
  ↓ No staged files match package.json [SKIPPED]
  ❯ Running tasks for yarn.lock
    ✖ yarn audit:moderate [FAILED]
↓ Skipped because of errors from tasks. [SKIPPED]
✔ Reverting to original state because of errors...
✔ Cleaning up...

✖ yarn audit:moderate:
error Command failed with exit code 2.
$ yarn audit --level moderate  /Users/y.shogoro/dev/src/bitbucket.org/wanocoltd/tunecorejapan_frontend/yarn.lock
5 vulnerabilities found - Packages audited: 2235
Severity: 5 Low
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
husky > pre-commit hook failed (add --no-verify to bypass)
```

これは、`yarn audit`が検知した脆弱性のレベルに応じて終了コードを変更しているからです。
https://classic.yarnpkg.com/en/docs/cli/audit/#toc-yarn-audit
> The command will exit with a non-0 exit code if there are issues of any severity found. The exit code will be a mask of the severities.
> 
> 1 for INFO
> 2 for LOW
> 4 for MODERATE
> 8 for HIGH
> 16 for CRITICAL

今回は、中程度の脆弱性から検知すると決めたので、exit codeが4以上の場合のみ以上終了とみなすscriptにします。

```package.json
{
  "scripts": {
    "audit:moderate": "yarn audit --level moderate || EXIT_CODE=$? && if [ $EXIT_CODE -ge 4 ]; then exit EXIT_CODE; else exit 0; fi;"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "yarn.lock": [
      "audit:moderate"
    ]
  },
}
```

以上で完成です。
これで、依存パッケージに変更があった際、中程度以上の脆弱性が自動でチェックされるようになりました。

# 余談
設定されている方も多いかと思いますが、`lint-staged`と`husky`を使った設定で、以下もおすすめです。

特に、なんでもかんでも`lint`かけたい気持ちなので`sort-package-json`はイチオシです。

- `json`や`md`, `yaml`などに`prettier`をかけて整形する
- `package.json`に`sort-package-json`をかけて、keyの並べ替えを行う
- `js`, `ts`に`jest --findRelatedTests`をかけて、関連するテストのみ実行する

```package.json
{
  "lint-staged": {
    "*.{json,md,yaml,yml}": [
      "prettier --write"
    ],
    "*.{js,jsx,ts,tsx}": [
      "eslint --fix",
      "jest --findRelatedTests"
    ],
    "package.json": [
      "sort-package-json"
    ],
  }
}
```
公式ページ
- [prettier](https://github.com/prettier/prettier) 
- [sort-package-json](https://github.com/keithamus/sort-package-json)
- [jest](https://github.com/facebook/jest)

# Wano 
Wanoは積極的にエンジニア採用 / デザイナー採用を行なっています！
まずはオンラインでVPoEとのカジュアル面談から。お好きな入り口からお気軽にお声がけください！

- Wano Recruitページ　https://group.wano.co.jp/recruit/
- QiitaJobs　https://jobs.qiita.com/employers/wano-inc/postings/1297
- Wantedly　https://www.wantedly.com/companies/wano/projects
- Findy　https://findy-code.io/companies/522
- CINRA.JOB　https://job.cinra.net/company/wano/