Compliance Intake · Dependency Ledger
License Drift: The Compliance Incident That Never Files a Ticket
A transitive dependency's license changes on a routine version bump. Nothing throws an error, nothing pages on-call, and the pipeline goes green exactly as it did yesterday. The first person to notice is usually legal or procurement — months later, after the package has already shipped to production a dozen times.
By Vimal Maheedharan — Technical Architect & SRE Consultant · September 2026
This isn't a legal lecture. It's a monitoring problem that most pipelines don't monitor. A license is metadata attached to a dependency the same way a CVE score is — it changes state, it has a severity, and it can turn a routine npm install into an obligation nobody agreed to. Treated as an observability gap instead of a contracts question, it's a problem platform and DevOps teams are already equipped to close.
| Package |
Declared License |
Observed Risk |
Status |
| ui-datepicker-lite |
MIT → BSL 1.1relicensed at v4.2.0, three minor versions back |
Production usage predates the change |
Flagged |
| pdf-render-core |
MITunchanged across last 9 releases |
None observed |
Cleared |
| queue-worker-utils |
Apache-2.0pulls in a GPL-2.0 fork two levels down |
Copyleft term inherited transitively |
Review |
| chart-fx |
Custom / no-compete clausefree to use, restricted on redistribution as a competing product |
Permissive-looking, not permissive |
Review |
| log-shipper |
AGPL-3.0commercial tier available, community tier is AGPL by default |
Network-use copyleft trigger |
Flagged |
LR-001
Silent Risk / Supply Chain
Unmonitored
The incident that doesn't page anyone
A transitive dependency's license changes — or a permissively licensed package gets relicensed to something restrictive — and no build fails, no alert fires, no dashboard turns red. This is exactly what happened at industry scale when Elastic moved Elasticsearch and Kibana from Apache 2.0 to the Server Side Public License, and again with MongoDB's SSPL shift years earlier: the software didn't change, only the terms governing its use did, and every downstream consumer inherited that change the moment they upgraded.
- A version bump is a legal event, not just a technical one. CI treats a patch release as routine by default; a license change hiding inside it gets the same free pass.
- Protestware made the risk adversarial, not just administrative. The colors.js and faker.js incident in early 2022 showed a maintainer could alter runtime behavior unilaterally — a reminder that trust in a dependency is a standing decision, not a one-time check.
- Nobody owns "notice the license changed" the way someone owns "notice the CVE dropped." Without an explicit owner, it defaults to nobody.
FindingLicense state is mutable and unmonitored in most pipelines — the same blind spot vulnerability scanning had before SCA tooling made it routine.
LR-002
Ownership / Process
Reframed
Why this is a DevOps problem, not just a legal one
Legal can write the policy — which licenses are acceptable, which trigger review, which are outright disallowed — but legal isn't in the dependency graph at 2am when a lockfile updates. That's the same argument that moved vulnerability scanning out of periodic audits and into CI. A Software Bill of Materials, in SPDX or CycloneDX format, is the artifact that makes license state queryable the way a vulnerability database makes CVE state queryable.
- An SBOM turns "what licenses are we running" into a query instead of an annual spreadsheet exercise reconstructed from memory.
- The discipline is identical to CVE scanning: continuous detection, a severity model, and a gate — not a point-in-time audit that's stale the moment it's filed.
- Policy belongs to legal; enforcement belongs to the pipeline. Splitting those cleanly is what keeps the check from becoming either toothless or a bottleneck.
FindingLicense compliance scales the same way security scanning does — by moving from a periodic review to a continuous, automated check with a clear owner.
LR-003
Scan Surface / Classification
Under Review
What to actually scan for
Permissive vs. copyleft is the binary most teams check for, and it's not enough. Four patterns cause most real incidents:
- License type changes on upgrade — a package that was MIT last release and isn't this release, whether by relicensing or a maintainer swap.
- Copyleft creep in transitive dependencies — a top-level Apache-2.0 package pulling in a GPL-licensed dependency three levels down, inheriting obligations nobody at the top level agreed to.
- Dual-licensed, "open core" packages — free under a copyleft license, commercially licensed for anything else; using the free tier in a proprietary product can trigger the exact terms it looked like you avoided.
- "Free but restricted" licenses — GSAP is a useful real-world example: it's free to use in most projects under its own terms, but historically restricted building a directly competing library on top of it. That's a use-case restriction a simple permissive/copyleft classifier will misread as clear.
FindingA binary permissive/copyleft check misses the two categories — dual-licensing and use-case restrictions — that cause the most surprising incidents.
LR-004
Enforcement / CI Gate
Gate Installed
Building it into CI
Once the policy exists — an allowlist, a review list, a denylist — enforcement is mechanical. Tools like license-checker for npm, FOSSA, and the OSS Review Toolkit (ORT) can walk a full dependency tree, including transitive dependencies, and classify every license found against policy.
- Fail the build on a denylisted license, the same way a critical CVE fails a security gate — not a warning buried in build logs nobody reads.
- Route review-list hits to a human instead of silently approving or silently blocking; ambiguous cases like use-case restrictions need judgment, not a rubber stamp.
- Re-run the scan on every dependency update, not just on a schedule — the whole point is catching drift at the moment it's introduced, not at next quarter's audit.
- Version the policy file itself so a denylist change is reviewable history, not a silent edit to what "compliant" means.
FindingLicense scanning belongs in the same gate as dependency vulnerability scanning — a failed check before merge, not a finding after release.
ci / license-gate.ts
exit 1 on violation
// Runs after `npm ls --all --json` produces the resolved dependency tree.
// Fails the pipeline on any denylisted or unreviewed license.
import { readFileSync } from "fs";
type Policy = {
allow: string[];
review: string[];
deny: string[];
};
type DependencyRecord = {
name: string;
version: string;
license: string;
};
function classify(dep: DependencyRecord, policy: Policy): string {
if (policy.deny.includes(dep.license)) return "DENY";
if (policy.review.includes(dep.license)) return "REVIEW";
if (policy.allow.includes(dep.license)) return "ALLOW";
return "UNKNOWN"; // unrecognized license — treat as a gate failure, not a pass
}
function runGate() {
const policy: Policy = JSON.parse(readFileSync("license-policy.json", "utf-8"));
const deps: DependencyRecord[] = JSON.parse(readFileSync("resolved-deps.json", "utf-8"));
const denied = deps.filter(d => classify(d, policy) === "DENY");
const unknown = deps.filter(d => classify(d, policy) === "UNKNOWN");
const review = deps.filter(d => classify(d, policy) === "REVIEW");
if (review.length) {
console.log(`${review.length} dependencies require manual review — routing to #license-review.`);
}
if (denied.length || unknown.length) {
console.error("License gate failed:");
[...denied, ...unknown].forEach(d =>
console.error(` ${d.name}@${d.version} — ${d.license}`)
);
process.exit(1);
}
console.log("License gate passed.");
}
runGate();
Audit Note
None of this replaces legal review — it replaces the assumption that legal review happens before every dependency update ships, which in practice it never does. The gate's job is narrow: catch drift the moment it enters the tree, route the ambiguous cases to a human, and let everything already cleared move at normal CI speed. The instinct to build is the same one that made vulnerability scanning routine — treat a changing piece of metadata as something worth watching continuously, not something worth discovering after the fact.
Next in this series
Schema migrations at scale — running them without locking the table your traffic depends on.