💡

【npm audit】npm package の脆弱性対応してますか?

2023/09/18に公開
1

■ はじめに

みなさんは自分の関わっているプロジェクトにおけるnpm packageの脆弱性対応をしたことがありますでしょうか?

今回、npm auditを利用して脆弱性対応をした内容をもとにnpm packageの脆弱性対応方法と対応手順をまとめてみました。

  • npm packageの脆弱性ってなに?
  • npm auditってなに?
  • npm audit fix --forceをやっても脆弱性が消えなかった
  • ライブラリとかpackageの依存関係てなに?

上記に当てはまる人にとっては読む価値のある内容になっていると思います!
それではいってみましょう!

■ そもそも脆弱性とは

ソフトウェアにおける脆弱性(ぜいじゃくせい)とは、プログラムの不具合や設計上のミスが原因となって発生した情報セキュリティ上の欠陥のことを言います。
この情報セキュリティ上の欠陥が存在すると、悪意ある人に勝手にプログラムを操作されたり、勝手に情報を盗んだりされる危険性があります。

https://www.soumu.go.jp/main_sosiki/joho_tsusin/security/basic/risk/11.html
https://ja.wikipedia.org/wiki/脆弱性

■ npm audit とは

npm auditとは、プロジェクトに含まれているnpm package郡の中に脆弱性のあるnpm packageが存在するかどうかをチェックして脆弱性のレベルなどを表示してくれるコマンドのことです。

auditには「監査」や「監査する」といった意味があります。

誰が npm package の脆弱性を確認してくれてるの?

npm package郡の中に脆弱性があるかどうかを確認してくれているのは、基本的にはnpm Inc(または親会社であるGitHub / Microsoft)が行っているようです。

ブログやニュースリリースで、新しいセキュリティ機能やアップデート、セキュリティ報告に関する情報が発表されている。
npm Blog Security
GitHub Blog

◎ npm audit を実行して中身を確認する

実際にあるプロジェクトを例にnpm auditコマンドを実行して表示される内容を確認していきます。

npm audit

表示された内容は、プロジェクト内に含まれるpackage

  • postcss
  • semver
  • word-wrap
    に脆弱性があることが示されており。
    加えて、合計14個、重要度レベルmoderateの脆弱性が発見されたことを示している。

※一部省略して記載しています。

# npm audit report

postcss  <7.0.36
Severity: moderate
Regular Expression Denial of Service in postcss - https://github.com/advisories/GHSA-566m-qj78-rww5
No fix available
# ...省略 (postcss の依存関係などが記載されている

semver  <5.7.2
Severity: moderate
semver vulnerable to Regular Expression Denial of Service - https://github.com/advisories/GHSA-c2qf-rxjj-qqgw
fix available via `npm audit fix --force`
Will install prettier-eslint-cli@7.1.0, which is a breaking change
# ...省略 (semver の依存関係などが記載されている

word-wrap  <1.2.4
Severity: moderate
word-wrap vulnerable to Regular Expression Denial of Service - https://github.com/advisories/GHSA-j8xg-fqg3-53r7
fix available via `npm audit fix`
# ...省略 (word-wrap の依存関係などが記載されている

14 moderate severity vulnerabilities
# ...省略 (npm audit fix などの注意事項もろもろが記載されている)

脆弱性の合計件数

最後の方に記載されているこの部分で合計14個、重要度レベルmoderateの脆弱性が発見されたことを示している。

14 moderate severity vulnerabilities

◯ 個々のライブラリの記述を細かく確認していく

このブロックがpostcssに関する内容です。

postcss  <7.0.36
Severity: moderate
Regular Expression Denial of Service in postcss - https://github.com/advisories/GHSA-566m-qj78-rww5
No fix available
# ...省略 (postcss の依存関係などが記載されている

package 名と脆弱性のあるバージョン

package名と脆弱性のあるバージョンが示されます。
postcssの場合、7.0.36より小さいバージョンに脆弱性があることを示しています。
そしてその7.0.36より小さいバージョンの postcssがプロジェクト内にあることを示しています。

postcss  <7.0.36 # 7.0.36より低いバージョンに脆弱性あり
semver  <5.7.2 # 5.7.2より低いバージョンに脆弱性あり
word-wrap  <1.2.4 # 1.2.4より低いバージョンに脆弱性あり

脆弱性の重要度レベル

重要度moderate(中程度)であることを示しています。
今回は3つともすべてmoderateでした。

Severity: moderate

重要度のレベルはこんな感じです。

レベル 説明
Critical いますぐ!
High できるだけ早く!
Moderate 時間が許す限り!
Low あなたの裁量次第で!

https://docs.npmjs.com/about-audit-reports

脆弱性の内容

脆弱性の重要度レベルの下の記述では、
どんな脆弱性を含んでいるかを教えてくれており、githubのリンクにはさらに詳しい内容が記載されています。

Regular Expression Denial of Service in postcss - https://github.com/advisories/GHSA-566m-qj78-rww5

訳:postcssにおける正規表現のサービス拒否

コマンドでの脆弱性修正方法

脆弱性の内容では、修正方法、コマンドで修正出来る場合はコマンドを、修正できない場合は修正できないことが記載されています。

# postcss はコマンドでは自動で直せないことを示している
No fix available
# semver は npm audit fix --force コマンドで修正できることを示している
fix available via `npm audit fix`
# word-wrap は npm audit fix コマンドで修正できることを示している
fix available via `npm audit fix --force`

修正方法は後に詳しく説明します。

https://docs.npmjs.com/cli/v10/commands/npm-audit

■ 脆弱性のある package を撲滅する

脆弱性のあるpackageが確認された場合大きく3つのアプローチになると思います。

npm audit fix
npm audit fix --force
③ 手動で直す

そして、対応の手順としては図で示したような流れになるかと思います。

これから、先程npm auditで確認された脆弱性を撲滅する対応をしていきます。

① npm audit fix

まず、npm audit fixでの修正を試みます。
npm audit fixコマンドはプロジェクト内にある脆弱性を自動で修正してくれるコマンドです。
先程のnpm audit reportword-wrapnpm audit fixで修正できることがわかりました。

# npm audit report
word-wrap  <1.2.4
Severity: moderate
word-wrap vulnerable to Regular Expression Denial of Service - https://github.com/advisories/GHSA-j8xg-fqg3-53r7
fix available via `npm audit fix`
# ...省略 (word-wrap の依存関係などが記載されている

npm ls で依存関係を確認する

npm audit fixを実行する前にword-wrapのプロジェクト内での依存関係と install されているバージョンを確認してみます。

npm ls word-wrap # word-wrap の依存関係ツリーを表示してくれるコマンド

バージョンが <1.2.4 のword-wrapが脆弱性をもつので、脆弱性があることが確認できました。

プロジェクト名
├─┬ jest-environment-jsdom@29.5.0
│ └─┬ jsdom@20.0.3
│   └─┬ escodegen@2.0.0
│     └─┬ optionator@0.8.3
│       └── word-wrap@1.2.3 deduped # <1.2.4 のため脆弱性あり
└─┬ prettier-eslint-cli@5.0.1
  └─┬ eslint@5.16.0
    └─┬ optionator@0.8.3
      └── word-wrap@1.2.3 # <1.2.4 のため脆弱性あり
package名 の横に表示される deduped とは

npmが特定packageが複数の依存関係がある場合に重複排除したことを示しているようです。

npmが、依存関係のツリー内で複数のpackageが同じpackageに依存している場合、そのpackageの単一のバージョンをインストールし、それを複数のpackageが共有することでディスクスペースを節約します。

今回のケースでは、word-wrap@1.2.3は、依存関係ツリーの異なる場所で二度参照されています。
しかし、実際には一度しかインストールされていないようです。
そのため、dedupedマークはそのpackageが重複排除されたことを示しています。

https://docs.npmjs.com/cli/v10/commands/npm-dedupe

npm audit fix で修正する

npm audit fixを実行します。

npm audit fix

すると、word-wrapの脆弱性が消えて、合計件数も減っていることが確認できました。

changed 1 package, and audited 1827 packages in 2s

247 packages are looking for funding
  run `npm fund` for details

# npm audit report

postcss  <7.0.36
Severity: moderate
Regular Expression Denial of Service in postcss - https://github.com/advisories/GHSA-566m-qj78-rww5
No fix available
...省略 (postcss の依存関係などが記載されている

semver  <5.7.2
Severity: moderate
semver vulnerable to Regular Expression Denial of Service - https://github.com/advisories/GHSA-c2qf-rxjj-qqgw
fix available via `npm audit fix --force`
Will install prettier-eslint-cli@7.1.0, which is a breaking change
...省略 (semver の依存関係などが記載されている

13 moderate severity vulnerabilities

修正されたので、word-wrapのインストールされているバージョンを確認してみます。
すると、1.2.3 -> 1.2.5にアップデートがされており、脆弱性のないバージョンになっていることが確認できました。

$ npm ls word-wrap

プロジェクト名
├─┬ jest-environment-jsdom@29.5.0
│ └─┬ jsdom@20.0.3
│   └─┬ escodegen@2.0.0
│     └─┬ optionator@0.8.3
│       └── word-wrap@1.2.5 deduped # <1.2.4 でないから脆弱性なし
└─┬ prettier-eslint-cli@5.0.1
  └─┬ eslint@5.16.0
    └─┬ optionator@0.8.3
      └── word-wrap@1.2.5 # <1.2.4 でないから脆弱性なし

git 差分を確認してみると、package-lock.jsonword-wrapに関連する箇所が書き換わっていました。

+++ package-lock.json
@@  @@
       }
     },
     "node_modules/word-wrap": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
-      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+      "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
@@  @@
       }
     },
     "word-wrap": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
-      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+      "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="
     },

どうやらpackage.lock.jsonを変更してpackageをインストールしなおしてくれたようです。
これで、word-wrapの脆弱性は撲滅できました!

② npm audit fix --force

npm audit fixで修正されなかった場合、次にnpm audit fix --forceでの修正を試みます。

npm audit fixでは、基本的にはメジャーバージョンの変更なしで解決できるものを対象範囲としています。
一方、
npm audit fix --forceでは、メジャーバージョンの変更が伴う形で解決できるものを対象範囲としているようです。
そのため、破壊的な変更など、現状のプロジェクトに対する互換性を無視した変更がされる懸念があります。

npm audit fixは比較的安全な解決手段といえる一方、
npm audit fix --forceによる自動修正はプロジェクトに問題が発生する懸念がないかどうかを確認しつつ慎重に対応する必要があるといえます。

それでは手順を踏みつつ、npm audit fix --forceを利用して修正していきます。
先程のnpm audit reportsemvernpm audit fix --forceで修正できることがわかりました。

# npm audit report

semver  <5.7.2
Severity: moderate
semver vulnerable to Regular Expression Denial of Service - https://github.com/advisories/GHSA-c2qf-rxjj-qqgw
fix available via `npm audit fix --force`
Will install prettier-eslint-cli@7.1.0, which is a breaking change
# ...省略 (semver の依存関係などが記載されている

npm ls で依存関係を確認する

npm audit fix --forceを実行する前にsemverのプロジェクト内での依存関係と install されているバージョンを確認してみます。

npm ls semver # semver の依存関係ツリーを表示してくれるコマンド

バージョンが <5.7.2 のsemverが脆弱性をもつので、脆弱性があることが確認できました。
semverはかなり多くのpackageに依存しておりだいぶ省略して記載しています。

プロジェクト名
# ...省略
├─┬ prettier-eslint-cli@5.0.1
│ ├─┬ eslint@5.16.0
│ │ ├─┬ cross-spawn@6.0.5
│ │ │ └── semver@5.7.2 deduped
│ │ └── semver@5.7.2
│ └─┬ prettier-eslint@9.0.2
│   └─┬ @typescript-eslint/parser@1.13.0
│     └─┬ @typescript-eslint/typescript-estree@1.13.0
│       └── semver@5.5.0 # <5.7.2 のため脆弱性あり
...省略

npm audit fix --force で修正する

npm audit fix --forceを実行します。

npm audit fix --force`

すると、semverの脆弱性が消えて、合計件数も減っていることが確認できました。

added 34 packages, removed 70 packages, changed 10 packages, and audited 1791 packages in 11s

252 packages are looking for funding
  run `npm fund` for details

# npm audit report

postcss  <7.0.36
Severity: moderate
Regular Expression Denial of Service in postcss - https://github.com/advisories/GHSA-566m-qj78-rww5
No fix available
# ...省略

7 moderate severity vulnerabilities
# ...省略

修正後に再度、semverのインストールされているバージョンを確認してみます。
すると、今回は内部で脆弱性のあるsemverに依存していたprettier-eslint-cli5.0.1 -> 7.1.0にアップデートがされています。
そして、prettier-eslint-cliの中に脆弱性のあるsemverが消えていることが確認できました。

プロジェクト名
# ...省略
├─┬ prettier-eslint-cli@7.1.0 # 5.0.1 -> 7.1.0 になっている
│ └─┬ @prettier/eslint@npm:prettier-eslint@15.0.1
│   ├─┬ @typescript-eslint/parser@5.62.0
│   │ └─┬ @typescript-eslint/typescript-estree@5.62.0
│   │   └── semver@7.5.4 deduped
│   └─┬ vue-eslint-parser@8.3.0
│     └── semver@7.5.4 deduped
# ...省略

git 差分を確認してみると、package.jsonが書き換わっており、prettier-eslint-cliがアップデートされていることが確認できました。
package-lock.jsonは差分が大き過ぎるため割愛

# package.json
@@  @@
  -    "prettier-eslint-cli": "^5.0.0",
  +    "prettier-eslint-cli": "^7.1.0",

prettier-eslint-cliをアップデートすることで、依存していたsemverの脆弱性をなくすことができました!
これで、semverの脆弱性は撲滅できました!

◯ 注意点

今回の自動で修正された内容は5系から7系へのメジャーバージョンアップデートです。
npm audit fix --forceはこのようなメジャーバージョンのアップデートも自動で実行されるので、変更後にサービスに与える問題がないかどうかを確認しながら慎重に対応する必要があります。

今回はprettier-eslint-cliが開発時のみに影響を与えるものであり、サービス自体には影響を与えない修正であると判断してそのままnpm audit fix --forceの修正を適用することとしました。

③ 手動で直す

最後にnpm audit fix --forceでも修正できなかったものについての対応をしていきます。
postcssの脆弱性がまだ消えていない状態です。

# npm audit report

postcss  <7.0.36
Severity: moderate
Regular Expression Denial of Service in postcss - https://github.com/advisories/GHSA-566m-qj78-rww5
No fix available
...省略

7 moderate severity vulnerabilities
...省略

npm ls で依存関係を確認する

postcssの依存関係とインストールされているバージョンを確認してみます。
すると、脆弱性のある<7.0.36のpostcssが存在していることが確認できました。

大本になるtyped-scss-modulesのなかでcss-modules-loader-coreを利用していて、
css-modules-loader-coreに依存しているpostcss
css-modules-loader-coreに依存している4つのpackageのなかで使われているpostcssに脆弱性があるようです。

プロジェクト名
├─┬ next@13.4.2
│ └── postcss@8.4.14
└─┬ typed-scss-modules@7.1.4
  └─┬ css-modules-loader-core@1.1.0
    ├─┬ postcss-modules-extract-imports@1.1.0
    │ └── postcss@6.0.23
    ├─┬ postcss-modules-local-by-default@1.2.0
    │ └── postcss@6.0.23
    ├─┬ postcss-modules-scope@1.1.0
    │ └── postcss@6.0.23
    ├─┬ postcss-modules-values@1.3.0
    │ └── postcss@6.0.23
    └── postcss@6.0.1

pacakge-lock.json を書き換える

package-lock.jsondependenciesにあるpostcssのバージョンを脆弱性のないものに書き換えます。

package-lock.json
@@ @@
"dependencies": {
-        "postcss": "^6.0.1"
+        "postcss": "7.0.36"
       }
@@ @@
"dependencies": {
         "postcss": {
-          "version": "6.0.23",
+          "version": "7.0.36",

...省略(何箇所もあるので同様に書き換える)

dependenciesのほかにrequiresの記述もありますが、書き換えなくてOKです。

// dependencies だけ書き換えればOK
"requires": {
  "postcss": "^6.0.1"
}
requires セクションには特定のpackageが依存しているリストが記載される
package-lock.json
"postcss": {
  "version": "6.0.23",
  "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz",
  "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==",
  "requires": {
    "chalk": "^2.4.1",
    "source-map": "^0.6.1",
    "supports-color": "^5.4.0"
  }

このように書かれていた場合、postcss

  • "chalk"(バージョン 2.4.1 以上)
  • "source-map"(バージョン 0.6.1 以上)
  • "supports-color"(バージョン 5.4.0 以上)
    が依存していることを示しています。

バージョンにつける「^」「~」が気になる方はこちらの記事も覗いてみてください。
https://zenn.dev/tm35/articles/778b9a260d43fc

npm install で package をインストールし直す

書き換えたpackage-lock.jsonをもとにpackageをインストールしなおします。

npm install

修正後に再度、postcssのインストールされているバージョンを確認してみます。
すると、書き換えた通りのバージョンがインストールされていることが確認できました。

プロジェクト名
├─┬ next@13.4.2
│ └── postcss@8.4.14
└─┬ typed-scss-modules@7.1.4
  └─┬ css-modules-loader-core@1.1.0
    ├─┬ postcss-modules-extract-imports@1.1.0
    │ └── postcss@7.0.36
    ├─┬ postcss-modules-local-by-default@1.2.0
    │ └── postcss@7.0.36
    ├─┬ postcss-modules-scope@1.1.0
    │ └── postcss@7.0.36
    ├─┬ postcss-modules-values@1.3.0
    │ └── postcss@7.0.36
    └── postcss@7.0.36

脆弱性が消えたかどうかnpm auditを実行すると、脆弱性が存在しないことが確認できました。

npm audit

found 0 vulnerabilities

プロジェクトから脆弱性を撲滅できました!

◯ 注意点

package-lock.jsonを直接書き換えるこの対応は正直、正攻法ではありません。

typed-scss-modules@7.1.4は記事執筆当初、最新バージョンです。
最新バージョンのpackageが脆弱性をもつpackageに依存しており、その依存しているpackageのバージョンを勝手に変えたものだからです。
特定のpackageに依存しているpackageのバージョンを勝手に書き換えた場合、互換性がなかったり、思わぬ不具合を生む可能性があります。

今回のように、利用しているpackageが最新バージョンであり、その依存関係に脆弱性のあるpackageがある場合、

  • package-lock.jsonを書き換えて脆弱性をなくす(今回のケース)
  • 脆弱性をもたない別のpackageを利用する
  • 対応を保留する(packageのメンテナーが脆弱性懸念をなくすのを待つ)

などの対応方法が考えられると思います。

今回このように対応した背景としては、package-lock.jsonを書き換えてpackageをインストールしなおしたあと、動作に問題がなかったことと、
対象のtyped-scss-modules自体が開発環境のみに利用しているpackageであり、直接サービスに影響を与える懸念がないと判断したためです。

どのように対応するべきかは
packageの脆弱性レベルとサービスに与える影響度合いを鑑みて柔軟に対応する必要がありそうです。

ちなみに npm info で package の依存関係と対応するバージョンを調べられる

npm info typed-scss-modules dependencies
# typed-scss-modules の依存関係と対応バージョン
{
  'bundle-require': '^3.0.4',
  chalk: '4.1.2',
  'change-case': '^4.1.2',
  chokidar: '^3.5.3',
  'css-modules-loader-core': '^1.1.0',
  esbuild: '^0.14.21',
  glob: '^7.2.0',
  joycon: '^3.1.1',
  'reserved-words': '^0.1.2',
  slash: '^3.0.0',
  yargs: '^17.3.1'
}
npm info css-modules-loader-core@1.1.0 dependencies
# css-modules-loader-core の v1.1.0 の依存関係と対応バージョン
{
  'icss-replace-symbols': '1.1.0',
  postcss: '6.0.1',
  'postcss-modules-extract-imports': '1.1.0',
  'postcss-modules-local-by-default': '1.2.0',
  'postcss-modules-scope': '1.1.0',
  'postcss-modules-values': '1.3.0'
}

■ さいごに

なかなか、脆弱性の対応やpackageのバージョン更新などは普通に開発をしていると後回しになってしまうことも多いと思います。
忙しいなかでも、週1や月1程度では、packageのバージョンや脆弱性をチェックする時間を持ちたいですね。

ありがとうございました!

参考資料

https://docs.npmjs.com/cli/v10/commands/npm-audit?v=true
https://docs.npmjs.com/cli/v10/commands/npm-ls?v=true
https://docs.npmjs.com/about-audit-reports
https://github.blog/category/npm/
https://github.blog/
https://zenn.dev/tm35/articles/778b9a260d43fc

Discussion

RuonRuon

素敵な記事ありがとうございます!npm auditの使い方やレポートの読み方、そして直す手順が詳しく記載されていて大変参考になりました!
一点コメントさせてください。「③ 手動で直す」の「pacakge-lock.json を書き換える」の代わりにpackage.jsonにoverridesセクションを追加してライブラリの依存関係を上書きするほうが良いかと思いました。(どちらにせよ依存ライブラリ内での思わぬ不具合を生む可能性はありますが。)
例:

package.json
  "overrides": {
    "postcss": "^7.0.36",
  },

また、その場合何を持って強制的なoverridesを承認するかも以下の点を確認するといいと思いました。

  1. overrideしたバージョンのリリースノートを参照し、破壊的変更がないか
  2. overridesしたバージョンでインターフェースの変更がないか(1とほぼ同じ)
  3. テストコードおよび、リグレッションテストを実施し、ハッピーパスが通るか。