Nobody chooses to accept a relicense. It arrives as a version bump: an
automated PR from Dependabot or Renovate, or a routine npm update,
carrying release notes about performance and bug fixes — and, unannounced in
the diff a reviewer actually reads, a different license field in the new
version's own package.json.
Both tools solve a different problem well: they watch a registry for a newer version and open a pull request with the version delta, changelog, and a compatibility signal. Neither diffs the license field between the version you have and the version they're proposing — it isn't part of what either was built to compare. A license change rides along inside an otherwise-routine version PR with nothing calling it out.
| Project | From | To | Date |
|---|---|---|---|
| MongoDB Server | AGPL-3.0 | SSPL-1.0 | 2018-10-16 |
| Elasticsearch & Kibana | Apache-2.0 | SSPL-1.0 / Elastic License | 2021-01-14 |
| Terraform | MPL-2.0 | BUSL-1.1 | 2023-08-10 |
| Redis | BSD-3-Clause | SSPLv1 / RSALv2 | 2024-03-20 |
Each of these is a widely used dependency, not an obscure one. The Terraform change alone drove the OpenTF Manifesto to more than 33,000 GitHub stars within a month and produced OpenTofu; the Redis change produced Valkey, forked and backed by the Linux Foundation within days. None of that visibility retroactively protected the projects that had already merged the version bump before anyone noticed.
Node / npm — reads each installed package's own
package.json, no network call:
node cli.mjs snapshot --out baseline.json
Python — pip-licenses already emits JSON; this reshapes its field names to match:
pip-licenses --format=json | \
jq '[.[] | {name: .Name, version: .Version, license: .License}]' \
> baseline.json
Go — go-licenses
emits a header-less import path,license URL,license name CSV,
which the tool reads directly (the last column is used as the license):
go-licenses csv ./... > baseline.csv
Rust — cargo-license's
JSON already uses name/version/license:
cargo license --json > baseline.json
Commit a reviewed baseline snapshot once, then compare against it on every PR that touches the lockfile:
- name: Check for dependency license drift
run: |
npm ci
node ventures/license-drift-check/cli.mjs snapshot --out /tmp/current.json
node ventures/license-drift-check/cli.mjs diff license-baseline.json /tmp/current.json
The command exits non-zero the moment any package's license changed
since the baseline, and 0 on a clean run or a change you've explicitly
accepted with --ignore. Nothing here decides whether a given
change is acceptable — it makes the change visible in the one place code
review already happens, instead of silent inside a version bump.
Each license is bucketed into permissive,
copyleft, or source-available by matching
well-known keywords in the license string — a skim aid, not a legal
determination. The one mix-up worth naming explicitly: colloquial "BSL"
almost always means the source-available Business Source License
(SPDX BUSL-1.1, the one Terraform, Sentry, and CockroachDB use)
— not the fully permissive Boost Software License
(SPDX BSL-1.0). The tool disambiguates on the version digit;
read the actual license text before relying on either label.
No, and mixing them up runs in both directions. Colloquial "BSL" almost always means the source-available Business Source License (SPDX BUSL-1.1), the one Terraform, Sentry, and CockroachDB use. SPDX BSL-1.0 is the unrelated, fully permissive Boost Software License.
No. It reports that a package's license string differs from a snapshot you captured earlier, and buckets the before and after into a public, well-known family as a skim aid. Whether a given change is acceptable for your project is a decision for you or your counsel, not this tool.
Yes. The diff engine reads a generic JSON array of {name, version, license} objects, or CSV with a name/version/license header, so pip-licenses, go-licenses, and cargo-license output all work with the commands above — only the snapshot command that scans node_modules directly is npm-specific.