Safeguard
Open Source Security

Building an Open-Source License Compliance Program That Flags Copyleft Risk in CI

Software Freedom Conservancy's suit against Vizio is headed to trial in August 2026 — proof that copyleft violations are litigated, not theoretical.

Safeguard Research Team
Research
6 min read

Software Freedom Conservancy sued Vizio in October 2021 in California state court, alleging that Vizio's SmartCast TVs ship GPLv2 and LGPLv2.1 code — including Linux and BusyBox — without providing the complete, compiling corresponding source required by those licenses, despite compliance gaps SFC says it first raised with Vizio back in 2018. The case is notable because SFC sued as a consumer and third-party beneficiary of the GPL rather than as a copyright holder, a novel theory that survived Vizio's motion for summary judgment in December 2023 and, after a ruling favoring SFC in December 2025, is now set for trial in August 2026. That timeline matters to every engineering team shipping open-source code: it shows that copyleft obligations are enforced years after the fact, against companies that shipped the code long before anyone noticed the mismatch. The industry's shared taxonomy for reasoning about that risk is the SPDX License List, maintained by the Linux Foundation and sitting at version 3.28.0 as of mid-2026, which most license-scanning tools use as their identifier registry. This post lays out how to turn that taxonomy into an automated, CI-enforced compliance program instead of a spreadsheet nobody updates.

What makes a license "copyleft," and why does the distinction matter for CI gates?

Copyleft licenses require that modifications — and in some cases anything linked or distributed alongside the code — be released under the same license, which is what creates legal exposure when copyleft code ends up inside proprietary or commercial products. The industry generally splits licenses into four risk tiers: permissive (MIT, BSD-2/3-Clause, Apache-2.0, ISC) carries low risk since it imposes only attribution; weak or file-level copyleft (LGPL-2.1, LGPL-3.0, MPL-2.0, EPL-2.0) only requires source disclosure for modifications to the licensed files themselves; strong copyleft (GPL-2.0, GPL-3.0) can require disclosing source for the whole combined work when distributed; and network copyleft (AGPL-3.0, SSPL-1.0) extends that obligation to software delivered only over a network, with no binary ever shipped. That last tier is the one SaaS teams most often miss, because "we never distribute a binary" was historically a safe assumption — the AGPL was written specifically to close that loophole.

Why isn't a one-time manual license audit enough?

A one-time audit is a snapshot, and dependency trees change on every npm install or pip install — a transitive dependency can flip from MIT to GPL-3.0 in a patch release without anyone on the team noticing, because most build tooling doesn't diff license fields between builds. The SFC v. Vizio case underscores why staleness is dangerous: SFC's complaint traces compliance gaps back to 2018, meaning the underlying GPL and LGPL obligations sat unaddressed for years before litigation followed. License metadata is also frequently wrong at the source — a package's package.json or pyproject.toml license field can say one thing while its actual LICENSE file says another, a mismatch that shows up often enough in real-world packaging that scanners treat it as its own finding category rather than an edge case. Manual audits, run quarterly or at release time, catch none of this drift between checks — which is exactly the gap a CI-integrated scan is built to close.

How do you encode a license policy so it's enforceable instead of just documented?

The fix is to express license policy the same way you express infrastructure — as declarative, version-controlled configuration, not a wiki page. A practical policy defines an allow list (permissive licenses your legal team has pre-cleared), a deny list (licenses that are non-starters for your distribution model, commonly GPL-3.0-or-later, AGPL-3.0-or-later, and SSPL-1.0 for proprietary SaaS), and a require-review list for licenses that are context-dependent, like LGPL-3.0-or-later or a custom license pattern. Safeguard implements this as a LicensePolicy custom resource in YAML, and critically supports context_overrides scoped to how the code is distributed — an SDK you hand to customers might deny LGPL-3.0 because dynamic-linking exceptions are murky in embedded contexts, while the same license is fine inside a server binary you never ship. That context-awareness is what separates a usable policy from a blanket ban that blocks half your dependency tree.

How does SPDX expression parsing prevent false positives and false negatives?

Modern packages increasingly declare compound SPDX expressions rather than a single identifier — MIT OR Apache-2.0 for dual-licensed code, (MIT AND Apache-2.0) where both apply simultaneously, or GPL-2.0-only WITH Classpath-exception-2.0, the exception GNU Classpath uses specifically to permit linking without triggering GPL's copyleft on the linking application. A scanner that treats these as opaque strings will either false-positive on GPL-2.0-only WITH Classpath-exception-2.0 (flagging code that's actually safe to link against) or false-negative on MIT OR GPL-3.0 (missing that GPL-3.0 is a valid resolution an author or downstream packager could choose). Correct evaluation means parsing the SPDX expression grammar, resolving OR clauses to the most permissive option that satisfies policy, treating AND clauses as requiring every listed license to individually pass, and recognizing named exceptions as modifying the base license's obligations rather than ignoring them.

What does automatic license discrepancy detection actually catch?

Detecting discrepancies means pulling license signal from every source a package exposes — manifest metadata, SPDX identifiers, full-text matching against LICENSE/COPYING/NOTICE files, source file headers, and even binary or container-layer extraction from /usr/share/doc/*/copyright — and flagging it when those signals disagree. A package.json declaring MIT while the shipped LICENSE file is AGPL is a real, documented packaging failure mode, and it's meaningfully different from a scan that only reads the manifest and reports MIT with false confidence. Reconciliation across sources also catches upstream relicensing: if a vendor's SBOM shows a dependency's license set changed between two releases, that's a signal worth reviewing even before you know whether the new license conflicts with your policy, because unexpected relicensing is itself a governance red flag independent of which license was chosen.

How Safeguard Helps

Safeguard runs multi-source license detection — package metadata, SPDX identifiers, full-text file matching, header scanning, and binary extraction — and reconciles them automatically, raising a license-discrepancy finding the moment two sources disagree instead of silently trusting whichever one a scanner happened to read first. Policy is expressed as a LicensePolicy YAML resource with allow, deny, and require-review lists plus context overrides, so an SDK distribution and a SaaS backend can enforce different rules from the same dependency tree, and Safeguard evaluates full SPDX compound expressions — OR, AND, and WITH exceptions — correctly rather than treating them as opaque strings. That policy plugs directly into Guardrails enforcement at three points: CI builds fail on a denied license in the dependency tree, PRs get a blocking check when a new direct dependency introduces one, and deployment admission blocks images containing denied licenses from ever reaching production. Safeguard also generates deterministic NOTICE and SPDX attribution documents from the same SBOM data in CI, so the artifact a legal team reviews is always in sync with what's actually shipping — the exact gap that turns a compliance question into an SFC v. Vizio–style dispute years later.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.