Claude CodeでOSSライセンスを一覧にしてGPL系の混入を点検する
package-lock.jsonの依存ごとのライセンス表記をSPDX識別子で一覧にし、GPL系の混入をClaude Codeで点検する手順です。ORとANDの読み分けや、法務に回す線引きも載せています。
依存パッケージのライセンス点検は、lockfileから表記を取り出して一覧にし、GPL系が混じる行だけを人が読む流れに落とすと回ります。Claude Codeには一覧の作成と分類の下書きを任せ、最終判断は法務に渡します。この記事では、npmのpackage-lock.jsonを例に、SPDX識別子の読み方、点検スクリプト、Claude Codeへの任せ方、法務に回す線引きを順に書きます。
点検の流れとClaude Codeの受け持ち
Claude Codeはコードベースを読み、ファイルを編集し、コマンドを実行するコーディング支援ツールです。ライセンス点検では、この「読む」と「コマンドを実行する」の部分がそのまま使えます。
点検の4段階
- 1
取り出す
lockfileから、依存ごとの名前・版・ライセンス表記を機械的に抜き出します。
- 2
揃える
表記をSPDX識別子と式に寄せ、旧表記や独自表記を洗い出します。
- 3
絞る
GPL系を含む行と、表記のない行だけを残します。
- 4
渡す
残った行に組み込み方と所見を添え、法務の確認依頼にします。
前半の2段階は決まった規則で処理できるので、スクリプトに寄せます。Claude Codeに向くのは後半で、残った行の依存経路をたどる、パッケージのREADMEやLICENSEファイルを読む、確認依頼の文面にまとめる、といった作業です。ライセンスが適合するかどうかの判断は、Claude Codeにも担当させません。
SPDX識別子とライセンス式の読み方
ライセンス表記を揃える基準になるのが、SPDX License Listです。ライセンスごとに短い識別子を割り当てた一覧で、各行には正式名のほか、FSFがFree/Libreとしているか、OSIが承認しているかの列が付いています。MITはMIT、Apache License 2.0はApache-2.0、Mozilla Public License 2.0はMPL-2.0です。
GPL系は「only」と「or-later」で別の識別子になる
GPLの識別子は、版と「その版のみ」か「それ以降も可」かで分かれます。
| 識別子 | 名称 |
|---|---|
GPL-2.0-only | 名称GNU General Public License v2.0 only |
GPL-3.0-only | 名称GNU General Public License v3.0 only |
GPL-3.0-or-later | 名称GNU General Public License v3.0 or later |
LGPL-3.0-only | 名称GNU Lesser General Public License v3.0 only |
AGPL-3.0-only | 名称GNU Affero General Public License v3.0 only |
SPDXの3.0で、GNU系の識別子は「この版のみ」「それ以降も可」を明示する形に置き換わりました。古いGPL-3.0やGPL-2.0、AGPL-3.0、LGPL-2.1は、3.0で非推奨になった識別子です。lockfileにこうした旧表記が残っていても、点検では「GPL系」として拾います。only系なのかor-later系なのかは、パッケージのLICENSEファイルを読まないと決まりません。
ORは選択、ANDは全部、WITHは例外付き
1つの依存に複数のライセンスが書かれているときは、SPDXのライセンス式として読みます。演算子は大文字で書く決まりで、小文字は演算子として扱われません。
MIT OR GPL-3.0-or-later: どちらかを選べる。選択肢を持つ側が使う条件を決めるLGPL-2.1-only AND MIT: 両方に同時に従う必要があるGPL-2.0-or-later WITH Bison-exception-2.2: 例外条項付きのGPLCDDL-1.0+: その版か、それ以降の版。+は識別子の直後に空白なしで付ける
点検で見落としやすいのは、GPL系の文字列が含まれるのに実際には選ばなくてよいORの行です。逆に、ANDの片側にGPL系があれば、その行は選択の余地がありません。一覧にするときは、この2つを別の分類にしておくと、読む順番を付けやすくなります。
npmのpackage.jsonとlockfileに入るライセンス表記
npmのpackage.jsonでは、licenseにSPDX識別子を書く運用が案内されています。複数ライセンスはSPDXのライセンス式の文字列で書き、例として(ISC OR GPL-3.0)が挙がっています。SPDXに識別子がない独自ライセンスは、SEE LICENSE IN <filename>という文字列で、同梱のファイルを指す書き方です。過去に使われたlicenses配列やオブジェクト形式は、有効なメタデータではないと説明されています。
つまり、lockfileから取れる表記は次の4種類に分かれます。
- 単一の識別子(
MIT) - ライセンス式(
(MIT OR GPL-3.0-or-later)) SEE LICENSE IN ...の独自表記- 表記なし、または旧形式
3と4は機械では判定できないので、必ず人が読む行に回します。
lockfileから一覧を作る
lockfileVersion 3のpackage-lock.jsonは、packagesの各エントリにlicenseを持ちます。npm 10.8.2でminimistとjszipだけを入れた小さなプロジェクトでpackagesを読むと、14パッケージ分の表記が取れました。内訳はMITが11件、ISCが1件、(MIT AND Zlib)が1件、(MIT OR GPL-3.0-or-later)が1件です。GPLの文字列が出てきたのは、jszipの1行だけでした。
次のスクリプトは、このlicenseを集計して、GPL系・旧表記・独自表記・表記なしの行だけを表にします。Node.js 20で動かしています。
// scripts/license-inventory.mjs
import { readFileSync } from "node:fs";
const lock = JSON.parse(readFileSync("package-lock.json", "utf8"));
const OLD_IDS = new Set(["GPL-2.0", "GPL-3.0", "LGPL-2.1", "LGPL-3.0", "AGPL-3.0"]);
function classify(license) {
if (!license) return "表記なし";
if (license.startsWith("SEE LICENSE")) return "独自ライセンス";
const ids = license.match(/[A-Za-z0-9.+-]+/g) ?? [];
if (ids.some((id) => OLD_IDS.has(id))) return "旧表記のGPL系";
if (/GPL/.test(license)) return /\sOR\s/.test(license) ? "GPL系を含むOR" : "GPL系";
return "ok";
}
const rows = Object.entries(lock.packages)
.filter(([path]) => path !== "")
.map(([path, p]) => ({
name: path.replace(/^.*node_modules\//, ""),
version: p.version,
license: typeof p.license === "string" ? p.license : "",
dev: p.dev === true,
}))
.map((r) => ({ ...r, status: classify(r.license) }));
const counts = new Map();
for (const r of rows) counts.set(r.license || "(なし)", (counts.get(r.license || "(なし)") ?? 0) + 1);
for (const [lic, n] of counts) console.log(`${n}\t${lic}`);
console.log("\n| パッケージ | 版 | ライセンス | devのみ | 分類 |\n|---|---|---|---|---|");
for (const r of rows.filter((r) => r.status !== "ok")) {
console.log(`| ${r.name} | ${r.version} | ${r.license || "-"} | ${r.dev ? "はい" : "いいえ"} | ${r.status} |`);
}実行すると、上の小さなプロジェクトでは次の出力になります。
node scripts/license-inventory.mjs11 MIT
1 ISC
1 (MIT OR GPL-3.0-or-later)
1 (MIT AND Zlib)
| パッケージ | 版 | ライセンス | devのみ | 分類 |
|---|---|---|---|---|
| jszip | 3.10.1 | (MIT OR GPL-3.0-or-later) | いいえ | GPL系を含むOR |jszipはMIT OR GPL-3.0-or-laterなので、MITを選べる行です。GPL系の文字列が出たからといって、ただちにGPLの義務を負うわけではありません。ただし、どちらを選ぶかを記録に残す必要はあります。この記録が、後で「どの条件で使っているか」を説明する材料になります。
lockfileに頼らず確かめたいときは、license-checker系のツールも使えます。license-checker-rseidelsohnを--summary付きで同じプロジェクトに実行すると、ライセンスごとの件数がMIT: 11、ISC: 2、(MIT OR GPL-3.0-or-later): 1、(MIT AND Zlib): 1の形で出ました。ISCがlockfile側より1件多いのは、このツールがプロジェクト自身を数えに含めているためで、依存の数は一致しています。
Claude Codeに任せる範囲を設定で絞る
点検のためにClaude Codeへ与える権限は、読み取りとスクリプト実行だけで足ります。インストールやネットワーク取得を許す必要はありません。権限ルールは.claude/settings.jsonのpermissionsに書き、評価は拒否、確認、許可の順です。最初に当たった規則で結果が決まります。
{
"permissions": {
"allow": [
"Read(package-lock.json)",
"Bash(node scripts/license-inventory.mjs)",
"Bash(npm ls *)"
],
"deny": [
"Bash(npm install *)",
"Bash(git push *)"
]
}
}Bash(npm run build)のように完全一致で書くと、そのコマンドだけが対象になります。*を付けると、後ろに引数が続く形も含められます。拒否ルールは、Claude Codeが普段出す形のコマンドを止めるためのものです。同じプログラムを別の書き方で呼ぶ場合までは防げないので、セキュリティ境界として頼らず、権限の実際の動きは/permissionsの管理で確認しておくと安心です。
CLAUDE.mdに判定の規約を書く
分類の規約はCLAUDE.mdに置いておくと、会話のたびに言い直さずに済みます。例えば次のような断片です。
## ライセンス点検の規約
- 一覧は scripts/license-inventory.mjs の出力を正とし、手で書き換えない
- ライセンス式は SPDX 式として読む。OR は選択可、AND は全部適用
- GPL / LGPL / AGPL を含む行、表記なし、SEE LICENSE IN の行は「要確認」に入れる
- 要確認の行には、依存経路(npm ls <名前>)と、配布物に入るかの所見を添える
- 適合・不適合は判断しない。所見は事実の列挙にとどめ、結論欄は空にするCLAUDE.mdは、@path/to/importの書き方で別ファイルを取り込めます。社内のライセンス方針書を取り込む手もありますが、取り込んだファイルも起動時にコンテキストへ載るので、長い規程を丸ごと読ませるのは避けたほうが無難です。CLAUDE.mdに規約を書くコツは、pytestの規約をCLAUDE.mdに教える例と同じで、実行コマンドと判定の線引きを具体的に書くことです。
依存経路をたどらせる
GPL系の行が見つかったら、その依存を誰が持ち込んでいるかを調べます。Claude Codeに次のように頼むと、npm lsの出力を読んで経路にまとめてくれます。
scripts/license-inventory.mjs を実行して、GPL系を含む行ごとに
npm ls <パッケージ名> --all で依存経路を調べ、
直接依存か間接依存か、devDependenciesだけか、を表にしてください。
適合かどうかの判断は書かないでください。表のうち、直接依存か間接依存かの区別と、devのみかどうかは、後の「組み込み方別の確認」で使います。
組み込み方別に確認する
ライセンスの扱いは、依存を成果物にどう組み込むかで変わることがあります。この点検でも、行ごとに組み込み方を添えます。ただし、どの組み込み方でどんな義務が生じるかは、各ライセンスの本文と社内の規程で決まる事項なので、ここでは分類の列だけを持ちます。
| 組み込み方 | 一覧に添える所見 |
|---|---|
| 配布する製品に含める | 一覧に添える所見配布物に含まれるファイルか(バンドル、同梱、コンテナイメージ) |
| 社内サービスとして動かす | 一覧に添える所見外部に提供するか、社内利用か |
| ネットワーク越しに提供する | 一覧に添える所見SaaSとして外部ユーザーに機能を出すか |
| ビルド・テストにだけ使う | 一覧に添える所見devのみか、成果物に混ざらないか |
lockfileのdev: trueは、開発時の依存であることを示す手がかりになります。ただし、devの依存が成果物に混ざるかどうかは、バンドラーの設定やDockerfileの作りで決まります。lockfileの印だけで「成果物に入らない」と書き切らず、ビルド成果物の中身を確かめた結果を所見に書きます。AGPLのようにネットワーク越しの提供が論点になるライセンスもあるので、サービス形態の列は残しておくと役に立ちます。
法務に回す線引き
Claude Codeに任せるのは「事実の整理」までです。適合の判断と、例外を認めるかどうかは法務や責任者に回します。回す条件を先に決めておくと、毎回の相談が早くなります。
法務に回す行の目安
GPL系が単独で書かれている
GPL、LGPL、AGPLのみ、またはANDで結ばれている行。選択の余地がありません。独自表記・表記なし
SEE LICENSE IN、旧形式、空欄の行。ライセンス本文を人が読む必要があります。ORに選択肢があるのに記録がない
MIT OR GPL-3.0-or-laterのような行。どちらを選んだかを記録に残します。旧表記のGPL系
GPL-3.0などの非推奨識別子。onlyかor-laterかをLICENSEファイルで確かめます。
法務への依頼には、パッケージ名と版、ライセンス表記、依存経路、組み込み方、成果物への影響の所見を1行にまとめて渡します。Claude Codeには、この依頼文の下書きまでを頼めます。結論の欄は空白にして、法務の回答を人が書き込む形にしておくと、判断者が誰かが記録に残ります。
定期的に回す
lockfileは依存の更新で変わるので、点検は1回で終わりません。claude -pを使うと、人が対話せずにスクリプトを呼び出して結果を整形できます。権限は--allowedToolsで絞ります。
claude -p "scripts/license-inventory.mjs を実行し、要確認の行だけを\
JSON配列にしてください" \
--allowedTools "Read,Bash(node scripts/license-inventory.mjs)" \
--output-format jsonclaude -pは、CLAUDE.mdなどを含めて対話セッションと同じコンテキストを読み込みます。起動を軽くしたいときは--bareを付けると、自動検出を省略できますが、その場合はCLAUDE.mdの規約も読まれません。規約を使いたいなら付けずに回すか、プロンプト側に規約を書きます。--allowedToolsの詳しい書き方は、-pモードの基本にあります。
更新PRの検証と組み合わせる場合は、RenovateとDependabotのPRを検証する運用に、この点検を1ステップとして足す構成が考えられます。新しい依存が増えた更新PRだけで、要確認の行が増えていないかを比べます。
点検でつまずきやすい点
ライセンス表記が古い、または実態と合わない
package.jsonのlicenseは、パッケージの作者が書いた申告です。LICENSEファイルの内容と食い違うことがあり、スクリプトの結果だけで確定させることはできません。GPL系が疑われる行は、LICENSEファイルの本文を人が読んで確かめます。
lockfileにlicenseが入っていない
licenseの欄がない行は、スクリプトではすべて「表記なし」に分類されます。件数が急に増えたら、lockfileを再生成するか、license-checker系のツールで補います。
npm以外のエコシステム
PythonやGo、Rustなどにも、それぞれのメタデータにライセンス欄があります。考え方は同じで、取り出し方のスクリプトだけ差し替え、SPDX識別子とライセンス式の読み方は共通で使えます。Rのように別の仕組みで依存を固定する例は、Claude CodeでRの解析を進める記事で扱っています。
まとめ
一覧の作成と絞り込みはスクリプトに寄せ、Claude Codeには依存経路の調査と依頼文の下書きを任せます。適合の判断は、事実を並べた表を受け取った法務が行います。この分担を、CLAUDE.mdの規約とpermissionsの設定に書き込んでおくと、依存が増えても同じ手順で点検を繰り返せます。