Software licenses: the risk that isn't a vulnerability
In plain terms#
Every open-source dependency you use comes with a license, the terms under which its author lets you use it. Most give you almost unlimited freedom. A few ask for something back, and a handful ask for something you may not be willing or able to give.
Nothing here is a security problem. Nobody breaks in through a license. But the consequences of getting it wrong land in a different place, like a customer's legal review, a due-diligence questionnaire during a funding round or acquisition, or a letter from someone whose code you shipped on terms you hadn't read.
Why it matters#
The uncomfortable part is that this can be more expensive than any vulnerability, and it's usually discovered at the worst possible moment.
A CVE is embarrassing, urgent, and fixable, because you upgrade the package. A license obligation you've been violating for two years is not fixable by upgrading, only by removing the dependency and rewriting what it did, or by complying with terms you never intended to accept, and you find out when an acquirer's lawyer runs a scan during diligence.
Small teams are the most exposed, because they add dependencies fastest and are the least likely to have anyone whose job is to notice.
The four kinds worth knowing#
Licenses get classified into categories. The names vary between tools; the shape doesn't.
Permissive. MIT, Apache-2.0, BSD, ISC. Use it, ship it, sell it. Keep the copyright notice somewhere. This is the overwhelming majority of what you depend on and it needs no thought.
Copyleft. GPL, LGPL, AGPL. You may use it freely, but if you distribute software that includes it, you must offer your own source code under the same terms. This is the one that can force a business decision.
File-level copyleft. MPL, EPL, CDDL. A middle ground where changes you make to those files must be shared, while your own code isn't affected, which is usually manageable.
Unidentified. The tool couldn't determine the license. Not necessarily bad, but it's the category most worth a human look, because nobody has read the terms.
When copyleft bites#
This is the part that's widely misunderstood, and it's why a blanket "no GPL" rule is often wrong and sometimes not strict enough.
The obligation in most copyleft licenses is triggered by distribution, which means handing the software to someone else:
| What you do | Does GPL oblige you to publish your source? |
|---|---|
| Run a web service your users log into | Usually no. You're not distributing anything. |
| Ship a desktop app, CLI, mobile app, or firmware | Yes, for GPL. This is distribution. |
| Ship a container image to a customer | Usually yes. |
| Use it internally, never ship it | No. |
| Run a web service using an AGPL dependency | Yes. AGPL closes the hosted-service gap deliberately. |
That table is why a hosted SaaS product and a company shipping an on-premise binary should have different license policies, and why a tool that fails your build on any copyleft license is wrong for one of them.
None of this is legal advice. The distinctions are real but the edges are contested, and "we link to it dynamically" arguments have been both accepted and rejected. If a copyleft dependency is load-bearing in something you distribute, that's a question for a lawyer, not a scanner.
"It's a dependency of a dependency" is not a defense#
This is the question every license audit reaches, and the answer surprises people.
Depth has no legal significance. No open-source license conditions its terms on how many edges lie between a component and the code you wrote. A copyleft library three levels down carries the same obligation as one you imported directly, if the same facts hold about how it's combined and whether you distribute it.
Nor does who published the repository it came from. If you ship or serve the result, the obligation is yours whoever assembled the tree, and consuming somebody else's project doesn't move that obligation onto them.
So why does everyone say deeper dependencies are lower risk? Because depth correlates with the things that decide it. Deeper packages are more often build- or test-only, more often reached through a wrapper rather than linked in, and more often replaceable without touching your code. That makes depth a reasonable way to order the work and a bad way to excuse it, and the failure is treating a useful ordering as an exemption.
Four facts decide an obligation, and none of them is depth:
| Do you distribute or convey it? | Internal use triggers nothing. Shipping does. |
| Do users reach it over a network? | What AGPL-3.0's section 13 is for, and it turns on whether you modified it. |
| How is it combined? | Static, dynamic or separate process. Contested, and a lawyer's question. |
| Does it ship at all? | A copyleft formatter that never reaches your artifact carries no distribution obligation, however alarming the row looks. |
That last one is worth its own sentence, because it is the one a scanner can help with and the one depth is the worst proxy for, since a
GPL formatter in devDependencies at depth 1 is safer than an AGPL runtime library at depth 4.
The standards have moved the same way. CISA's SBOM minimum elements dropped the old depth element, which asked only for top-level dependencies, in favor of coverage, which asks for all components including transitive ones with no minimum depth, and made a component's license a minimum element in the same revision. "It was three levels down" was already not a legal argument, and it is not a documentation argument either.
Where it fits#
License checking belongs next to dependency scanning, because it reads the same thing, your dependency tree. It's usually cheap to run and produces very few findings once permissive licenses are filtered out.
The natural gate is a pull request. A new dependency is exactly when the question is cheap to answer, since swapping a library on the day you added it costs an hour and doing it two years later costs a rewrite.
Common pitfalls#
Treating every license as a finding. Most tools report all of them. A list of two hundred rows saying "MIT is fine" is how people learn to ignore license output.
Blanket-banning copyleft without asking whether you distribute. For most hosted services the GPL obligation never triggers, and a rule that fails builds for no reason gets switched off, along with the checks that did matter.
Ignoring transitive dependencies. Your direct dependency is MIT; the thing it pulls in is not. The tree is what matters, and depth is not a defense.
Checking the source tree and not the images. A license obligation lives wherever the code does. A base image you did not build carries its own dependency tree, and it is exactly the one nobody can check by reading a manifest, because the manifest belongs to somebody else.
Assuming the license file is the license. Packages misdeclare, dual-license, or change terms between versions. What's in your lockfile today is what you're bound by today.
Leaving it until diligence. The cost of a license problem is roughly proportional to how long it's been in your tree.
licenses control ships today, backed by Trivy, with Mend available as an opt-in second scanner. It reads your repositories and your images, because the question of what you are obliged by has no target kind in it, and an obligation inside a base image you only run is still yours if you ship or serve the result. It reports the licenses that carry an obligation and stays quiet about the rest. On Draugr's own
repository, measured on 7 August 2026, that is the difference between 78 findings and none. Permissive licenses aren't hidden; they're inventory, and config.sbom records one per package.Copyleft is a warning by default rather than a build failure, because whether it applies depends on whether you distribute, which your descriptor does not say. If you ship binaries, tighten it with
deny: ["GPL-3.0-only"], which makes it an
error. License policy gates independently of vulnerability policy, so you can fail a build on a forbidden license while a medium CVE stays a warning.Where the code is somebody else's,
builtBy: upstream on the repository, the image or the whole component changes what you are told to do about it. Not "change the code", which you cannot, but the
component to replace or raise with its publisher. It does not change what the policy accepts,
which would be the wrong lesson, since the obligation is yours either way.When you decide a license is acceptable, that decision is recorded rather than deleted.
config.exclude requires a reason, and the finding stays in the report marked with it. Which is what an auditor is asking for. Not whether the scanner ran, but who decided this was
fine, and when. See the Saga reference.
Keep learning#
- SCA, the other half of dependency risk, which is known vulnerabilities
- SBOM, the inventory that records every license you ship
- Shift left, and why the pull request is the cheap moment to ask
Related#
License risk is a compliance question rather than a security one, so it is decided the way other compliance questions are. See risk acceptance for how an exception is recorded, and audit evidence for what makes the resulting report count.