npmパッケージのライセンス管理を自動化する:node_modules の構造と license-checker の活用
はじめに
受託開発やウェブ制作の案件において、納品時に利用ライセンスの一覧表(OSS利用状況確認書など)の提出を求められるケースは少なくありません。
クライアントがこの資料を求める背景には、法務コンプライアンス(著作権表示やソースコード開示義務の遵守)と、セキュリティ(脆弱性管理)の二つの観点があります。ただし、本記事で扱うのは前者のライセンス面のみです。後述する license-checker は依存パッケージのメタデータを読み取るツールであり、脆弱性の検知は行いません。脆弱性管理との接続については、記事後半の SBOM のセクションで扱います。
しかし、package.json を確認しながら手作業で Excel やスプレッドシートにパッケージ名やライセンス種別(MIT, Apache 2.0, GPL等)を書き写す運用には、効率面・安全面の両方で明確な課題が存在します。
この記事では、手動管理の課題と、license-checker を活用したライセンス抽出・CI/CD 化の手法、そして自動化では解決できない範囲について解説します。
手動によるライセンス管理が抱える課題
package.json に記載されているのは、開発者が直接インストールした「直接依存(Direct Dependencies)」のみです。
しかし、実際には1つのパッケージが内部で複数のサブパッケージに依存しているため、npm install を実行すると多くの「間接依存(Transitive Dependencies)」が node_modules 内に引き込まれます。
[アプリケーション]
└── 直接依存パッケージ (例: MIT)
├── サブパッケージ A (例: Apache-2.0)
└── サブパッケージ B (例: GPL-3.0-or-later) ※手動確認では見落としやすい
手動で package.json のみを一覧化した場合、間接依存に含まれる GPL や AGPL といったコピーレフト型ライセンスが一覧から漏れます。ここで問題になるのはライセンスそのものではなく、利用条件を把握しないまま頒布してしまうことです。
なお、コピーレフト型ライセンスは「商用利用を禁止するライセンス」ではありません。GPL は商用利用そのものを制限しておらず、頒布(distribution)を行った際に派生物のソースコード開示義務が生じる、という条件が付くものです。条件を理解し、それを満たしたうえで利用すれば何も問題はありません。一覧化の目的は、この判断に必要な情報を漏れなく揃えることにあります。
license-checker による依存関係のスキャン
Node.js エコシステムでは、インストールされた全パッケージのメタデータ(package.json や LICENSE ファイル)が node_modules 以下に保持されています。
CLIツールである license-checker-rseidelsohn を使用することで、node_modules 全体を再帰的にスキャンし、すべての依存ライセンスを一括で確認できます。
node_modules から何を読み取っているのか
このツールは node_modules ディレクトリを走査し、各パッケージの package.json の license フィールドを SPDX 識別子として解釈します。ここで判定できなかった場合は、LICENSE → LICENCE → COPYING → README の順にファイルを探索し、既知のライセンス本文と照合して推測します。
この推測によって判定されたライセンスには、出力上でライセンス名の末尾に * が付与されます。どのファイルからも判定できなかった場合は UNKNOWN として出力されます。
└─ debug@2.0.0
├─ repository: https://github.com/debug-js/debug
└─ licenses: MIT* ← package.json 以外から推測された判定
つまり、抽出結果の確からしさは上流パッケージのメタデータの品質に依存します。この前提は、後述の「注意点と限界」で改めて扱います。
基本的なコマンド実行
プロジェクト配下で以下のコマンドを実行すると、本番環境(--production)に必要なライセンス内訳のサマリが出力されます。
npx license-checker-rseidelsohn@4.3.0 --production --summary
バージョンを固定しているのは、CI での再現性を確保し、意図しない更新の混入を避けるためです。継続的に使用する場合は devDependencies に追加しておくとよいでしょう。
出力例:
├─ MIT: 312
├─ Apache-2.0: 24
├─ BSD-3-Clause: 8
└─ GPL-3.0-or-later: 1
クライアント提出用フォーマット(CSV)の自動生成
提出用資料として CSV または Excel 形式が指定されている場合も、手入力を行う必要はありません。
ただし、--csv のデフォルト出力に含まれるのはモジュール名・ライセンス種別・リポジトリURLの3列のみです。発行元やライセンスファイルのパスなどを含めたい場合は、--customPath で出力する列を定義します。
{
"name": "",
"version": "",
"licenses": "",
"repository": "",
"publisher": "",
"licenseFile": ""
}
実行コマンド:
npx license-checker-rseidelsohn@4.3.0 --production --csv \
--customPath license-format.json \
--out oss_licenses.csv
customPath で指定できる項目には、上記のほか copyright、email、description、url、licenseText などがあります。なお、module_name 列は指定の有無にかかわらず常に先頭列として出力されます。
手動運用と自動生成の比較
| 評価項目 | 手動転記(Excel運用) |
license-checker 自動生成 |
|---|---|---|
| 作業時間 | 数時間(大規模プロジェクトほど増加) | 数秒〜数十秒 |
| 対象範囲 | 直接依存(package.json)のみになりがち |
間接依存を含む node_modules 全体 |
| 抽出漏れ・誤記 | 人為的ミス(入力漏れ・誤記)が発生する | 発生しない(ただし上流メタデータの誤りは検知不可) |
ライセンス本文の収集(帰属表示義務への対応)
一覧表の提出だけで対応が完了すると考えがちですが、MIT・BSD・Apache-2.0 といった寛容型(permissive)ライセンスの多くは、頒布物に著作権表示とライセンス本文を含めることを条件としています。一覧表はクライアントの管理資料としては機能しますが、これ自体はライセンス条件の履行にはあたりません。
--files オプションを使用すると、各パッケージのライセンスファイルを指定ディレクトリへ module-name@version-LICENSE.txt の形式で収集できます。
npx license-checker-rseidelsohn@4.3.0 --production --files ./licenses
収集したファイルを結合し、NOTICE や THIRD-PARTY-LICENSES.txt として成果物に同梱する運用にしておけば、納品物側の要件も同時に満たせます。
フロントエンドでは「バンドル成果物」を基準にする
dependencies に存在するパッケージが、そのまま最終成果物に含まれるとは限りません。Vite(Rollup)や webpack のビルドでは Tree Shaking によって未使用コードが除去されるため、dependencies 全体と、実際にクライアントへ配布されるコードは一致しません。
帰属表示の対象は、原則として実際に頒布したコードです。そのためフロントエンドの成果物については、node_modules を起点にするよりも、バンドラのプラグインでバンドル結果から直接ライセンス情報を生成するほうが正確です。
-
Vite / Rollup:
rollup-plugin-license -
webpack:
webpack-license-plugin
抽出結果の検証
自動化の対象は「転記作業」であって「確認作業」ではありません。生成した一覧をそのまま提出する前に、判定できなかったパッケージを洗い出します。
npx license-checker-rseidelsohn@4.3.0 --production --onlyunknown
ここで検出されたパッケージについては、リポジトリの LICENSE ファイルを直接確認します。上流のメタデータが誤っている、あるいは記載されていないケースでは、--clarificationsFile を使って個別に正しい値を上書きできます。
{
"some-package@1.2.3": {
"licenses": "MIT",
"licenseFile": "node_modules/some-package/LICENSE.md"
}
}
このファイルをリポジトリで管理しておけば、確認済みの判断がチーム内に蓄積され、次回以降の納品時に同じ調査を繰り返さずに済みます。
CI/CD パイプラインでの継続的なライセンス監査
納品時の単発作業で終わらせず、開発の過程で条件の確認が必要なライセンスが混入した段階で検知する仕組みを構築するのが理想的です。
拒否リストではなく許可リストを使う
--failOn で対象ライセンスを列挙する方法が広く紹介されていますが、この方式には見落としが生じます。判定はライセンス文字列の完全一致で行われるため、以下のようなケースをすべて自力で予測して列挙する必要があるためです。
- SPDX では
GPL-3.0は非推奨識別子となっており、現在はGPL-3.0-only/GPL-3.0-or-laterが使われる -
LGPL-2.1-or-later、AGPL-3.0-onlyなど、系統ごとに表記が分岐する -
(MIT OR GPL-3.0-only)のような複合式は、単独の識別子とは一致しない
{
"scripts": {
"license:check": "license-checker-rseidelsohn --production --onlyAllow 'MIT;ISC;Apache-2.0;BSD-2-Clause;BSD-3-Clause;0BSD;CC0-1.0;Unlicense'",
"license:csv": "license-checker-rseidelsohn --production --csv --customPath license-format.json --out oss_licenses.csv"
}
}
なお --onlyAllow / --failOn の区切り文字はセミコロン(;)です。ライセンス名自体にカンマが含まれる場合があるため、カンマ区切りでは正しく解析されません。
許可リスト方式では、未知のライセンスや複合式が出現するたびに CI が失敗します。これは運用上のノイズになりますが、「気づかないまま通過する」よりも「気づいて判断を記録する」ほうが望ましい挙動です。
CI が落ちたあとの承認フローを決めておく
ここが実運用でもっとも形骸化しやすい部分です。
運用フローの例です。
- CI が失敗する。開発者はその場で許可リストに追記しない
- 対象パッケージの
LICENSEと、自プロジェクトの頒布形態(配布あり/SaaS のみ)を確認する - 法務または OSS 管理者へエスカレーションする
- 承認された場合は、理由を添えて許可リストを更新する PR を出す。承認されない場合は代替パッケージへ差し替える
- PR の履歴が、そのまま判断の記録として残る
このフローを機能させるには、許可リストをレビュー可能な単位で切り出しておく必要があります。package.json の scripts に直接書き込んだままでは、レビュー対象を絞り込めません。
MIT;ISC;Apache-2.0;BSD-2-Clause;BSD-3-Clause;0BSD;CC0-1.0;Unlicense
{
"scripts": {
"license:check": "license-checker-rseidelsohn --production --onlyAllow \"$(cat license-allowlist.txt)\""
}
}
/license-allowlist.txt @your-org/legal @your-org/oss-admin
CODEOWNERS で保護しておけば、許可リストへの追加時に指定したレビュアーの承認が必須になります。なお $(cat ...) は sh 系シェルを前提とした記法です。Windows 環境でも実行する場合は、Node のラッパースクリプト経由で読み込む形にしてください。
GitHub Actions での自動実行
CI ワークフローに上記スクリプトを組み込むことで、Pull Request が作成された際に自動でライセンス監査が走ります。あわせて一覧表を成果物として保存しておけば、納品用データが常に最新の状態で取得できます。
name: License Check
on: [pull_request]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
- run: npm ci
- run: npm run license:check
- name: ライセンス一覧の生成
if: always()
run: npm run license:csv
- uses: actions/upload-artifact@v4
if: always()
with:
name: oss-licenses
path: oss_licenses.csv
(各 Action のバージョンは執筆時点のものです。導入時に最新のメジャーバージョンを確認してください。)
SBOM への移行を見据える
CSV は人間が確認するための形式です。長期的には、標準フォーマットの SBOM(Software Bill of Materials)を成果物として出力する構成へ移行することを推奨します。理由は三点あります。
-
単一ツールへの依存を減らせる: 後述のとおり
license-checker-rseidelsohnは更新頻度が低下しています。標準フォーマットであれば、生成ツールを差し替えても提出物の形式は変わりません。 - 脆弱性管理と接続できる: 冒頭で切り分けた「脆弱性管理」は、この段階で同じ成果物の上に載せられます。
- 提出形式として通用する: サプライチェーン管理の要求から、SBOM そのものの提出を求められる場面が増えています。
npx @cyclonedx/cyclonedx-npm --omit dev --output-file bom.json
生成した SBOM は、Trivy などのスキャナにそのまま読み込ませることができます。
trivy sbom bom.json
これにより、ライセンス情報と脆弱性情報を同一の成果物から扱えるようになります。クライアントの要求水準に応じて、人間向けの CSV と機械可読な SBOM を併用する構成を検討するとよいでしょう。
注意点と限界
自動化によって解決できる範囲を正しく理解しておくことが重要です。
- ツールの出力は法務レビューの代替ではありません: 抽出できるのは、あくまでパッケージが自己申告したメタデータです。デュアルライセンスの選択、社内ポリシーとの適合判断、契約条件との整合といった判断は人間の領域に残ります。
-
--productionの判定は宣言依存です:dependenciesとdevDependenciesの分類は開発者の記述に基づくため、ビルド時のみ使用するツールがdependenciesに置かれていれば、それも一覧に含まれます。フロントエンドにおける Tree Shaking との関係は前述のとおりです。 -
ツール自体のメンテナンス状況にも留意が必要です:
license-checker-rseidelsohnはメンテナ自身が更新頻度の低下を明言しています。実運用に組み込む際は、pnpm licenses list、CycloneDX、あるいは商用のライセンス管理サービスなど、代替手段も含めて選定することを推奨します。
まとめ
-
間接依存まで把握する:
package.jsonの直接依存だけでは、実際に頒布するコードの利用条件を把握できない。 -
抽出は自動化できる:
node_modules内のメタデータを CLI でスキャンすることで、提出用データを即座に生成できる。 - 一覧化とライセンス本文の同梱はセット: 寛容型ライセンスの帰属表示義務は、一覧表の提出だけでは満たされない。フロントエンドはバンドル成果物を基準にする。
- 検知は許可リストで、更新は承認フローで: 拒否リストは識別子の表記揺れを取りこぼし、レビューを伴わない許可リストは形骸化する。
- 自動化の範囲を明示する: 転記作業は自動化できるが、上流メタデータの検証と最終的な適合判断は人間に残る。
開発現場における定型的な手作業は、仕組み化によって効率化とリスク低減を同時に達成できます。そのうえで、仕組みが何を保証していないのかを把握しておくことが、実務では同じくらい重要です。
Discussion