← All articles

The license nobody gated on: copyleft in your dependency tree

August 7, 2026 · Wilson Santos

Every dependency you pull in arrives with terms. Most of them ask for nothing you would object to. A few ask for something back, and the asking is not enforced by anything in your pipeline, no scanner fails, no alert fires, no dashboard turns amber.

So the failure mode is not a breach but a lawyer, during due diligence, running a scan you never ran.

Why this is worse than a CVE, structurally#

A vulnerability is embarrassing, urgent and fixable by upgrading. You bump the package and the finding goes away. The whole industry is built around that loop.

A license obligation is none of those things. If you have been shipping a binary that links something under GPL-3.0 for two years, the fix is not an upgrade but removing the dependency and rewriting what it did, or complying with terms nobody in the building intended to accept. And the discovery moment is the worst one available, whether that is an acquirer's counsel, a customer's legal review, or a questionnaire in a procurement cycle you were about to close.

The asymmetry is that security has a fix costing a version bump. Compliance has a fix that costs an engineering quarter, and it accrues silently the entire time nobody is looking.

The thing most tools get wrong#

Point a license scanner at a repository and it will, by default, tell you about every license it found. On Draugr's own dependency tree, measured on 7 August 2026, that is 78 licenses of which zero carry any obligation, across four distinct licenses, all permissive. Re-run it yourself; the ratio is the point and it will not have moved much.

That is not a report but a haystack somebody built on purpose.

Permissive licenses are inventory, not findings. MIT, Apache-2.0, BSD. Every dependency has one, and listing them all buries the two that matter under seventy-five that do not. The inventory question is real, and it has its own answer in an SBOM, with a license per package, generated on every release. It is a different artifact for a different reader.

A license finding should mean that this one carries an obligation and somebody needs to decide about it.

Gating on it#

Draugr's licenses control reports the licenses that carry an obligation, meaning copyleft, forbidden or unidentified, and stays quiet about the rest. Trivy does the reading; you supply the judgement:

config:
  controllers:
    licenses:
      enabled: true
      deny: ["GPL-3.0-only", "AGPL-3.0-only"]
      warn: ["LGPL-3.0-only"]

deny and warn name SPDX ids and beat the category, and that ordering is the important part. Whether copyleft is a problem depends on whether you distribute. A company shipping a binary to customers and a company running a service have different answers, and no scanner can work out which you are. Trivy cannot know. You always do.

That is also why restricted is a warning by default rather than an error. It is not a hedge; it is the honest position for a rule whose answer depends on a fact the descriptor does not contain.

Gate it separately from your CVEs#

This is the part teams skip, and it is the reason license policy so often ends up unenforced.

License policy is usually owned by different people than security policy, whether legal or the office of the CTO rather than the security team, and it has a different tolerance. "Fail the build on a forbidden license, but only warn on a medium CVE" is a completely coherent position, and most tools make it impossible to express because everything shares one severity threshold.

config:
  gate:
    controls:
      licenses: high       # a denied license fails the build
  # …while --fail-on stays wherever you had it for vulnerabilities

Two owners, two thresholds, one pipeline. If your license rule has to share a threshold with your CVE rule, one of them is going to be set wrong, and it will be the one whose owner is not in the room when the threshold gets tuned.

When the scanner does not speak SPDX#

A practical wrinkle, because it will bite anyone who swaps scanners. Draugr's licenses control also runs on Mend, and Mend frequently reports licenses by its own names, BSD 3 rather than BSD-3-Clause, Apache 2.0 rather than Apache-2.0.

A policy written in SPDX silently matches none of them. Draugr's answer is to use Mend's SPDX name where it supplies one, carry Mend's own name where it does not, and warn, listing the identifiers that run produced, so you write rules against the strings that will appear rather than the ones you assumed.

The alternative was a translation table, and it is worse. It would be consulted exactly where there is least evidence, the cases the scanner itself declined to map, and a wrong entry applies your policy to the wrong license. A policy that matches nothing is visibly broken. A policy quietly matching the wrong thing is not.

The short version#

License risk is the compliance question that behaves least like a security one. It accrues silently, it is not fixed by upgrading, and it surfaces in front of the people you least want to be surprised in front of.

Report only what carries an obligation. Gate it on its own threshold, owned by whoever owns the answer. And write the policy in whatever vocabulary your scanner emits.

The licenses guide covers what the license families oblige you to do; the SBOM guide covers the inventory half. Draugr's control catalog has the configuration in full.