【npm audit】npm package の脆弱性対応してますか?
■ はじめに
みなさんは自分の関わっているプロジェクトにおけるnpm package
の脆弱性対応をしたことがありますでしょうか?
今回、npm audit
を利用して脆弱性対応をした内容をもとにnpm package
の脆弱性対応方法と対応手順をまとめてみました。
npm package
の脆弱性ってなに?npm audit
ってなに?npm audit fix --force
をやっても脆弱性が消えなかった- ライブラリとか
package
の依存関係てなに?
上記に当てはまる人にとっては読む価値のある内容になっていると思います!
それではいってみましょう!
■ そもそも脆弱性とは
ソフトウェアにおける脆弱性(ぜいじゃくせい)とは、プログラムの不具合や設計上のミスが原因となって発生した情報セキュリティ上の欠陥のことを言います。
この情報セキュリティ上の欠陥が存在すると、悪意ある人に勝手にプログラムを操作されたり、勝手に情報を盗んだりされる危険性があります。
■ 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 | あなたの裁量次第で! |
脆弱性の内容
脆弱性の重要度レベルの下の記述では、
どんな脆弱性を含んでいるかを教えてくれており、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`
修正方法は後に詳しく説明します。
■ 脆弱性のある package を撲滅する
脆弱性のあるpackage
が確認された場合大きく3つのアプローチになると思います。
① npm audit fix
② npm audit fix --force
③ 手動で直す
そして、対応の手順としては図で示したような流れになるかと思います。
これから、先程npm audit
で確認された脆弱性を撲滅する対応をしていきます。
① npm audit fix
まず、npm audit fix
での修正を試みます。
npm audit fix
コマンドはプロジェクト内にある脆弱性を自動で修正してくれるコマンドです。
先程のnpm audit report
でword-wrap
がnpm 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
が重複排除されたことを示しています。
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.json
のword-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 report
でsemver
がnpm 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-cli
が5.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.json
のdependencies
にある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が依存しているリストが記載される
"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 以上)
が依存していることを示しています。
バージョンにつける「^」「~」が気になる方はこちらの記事も覗いてみてください。
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
のバージョンや脆弱性をチェックする時間を持ちたいですね。
ありがとうございました!
参考資料
Discussion
素敵な記事ありがとうございます!npm auditの使い方やレポートの読み方、そして直す手順が詳しく記載されていて大変参考になりました!
一点コメントさせてください。「③ 手動で直す」の「pacakge-lock.json を書き換える」の代わりにpackage.jsonに
overrides
セクションを追加してライブラリの依存関係を上書きするほうが良いかと思いました。(どちらにせよ依存ライブラリ内での思わぬ不具合を生む可能性はありますが。)例:
また、その場合何を持って強制的なoverridesを承認するかも以下の点を確認するといいと思いました。