OSS License Compliance: Break Your Build, Not the Law

Header image: Mountain valley in Kluane National Park and Reserve of Canada

Open source licensing

When you make a creative work (such as writing, graphics, or code), that work is under exclusive copyright by default. That is, the law assumes that as the author of your work, you have a say in what others can do with it.

In general, that means nobody else can use, copy, distribute, or modify your work without being at risk of take-downs, shake-downs, or litigation.

Authors of open-source software (OSS) expect others to use, modify, and share their work. They must explicitly give these permissions with a license because the legal default without one is still exclusive copyright.

Users of open source avoid wasting time solving already-solved problems. Most open source is published to and installed from the package registries of different programming languages, such as PyPI, the Rust package registry, or NPM, the largest package registry hosting more than 3.4 million JavaScript packages at the time of writing.

Rights and obligations

Any open source user, whether individual or company, who fails to take software license compliance seriously, risks serious harm. While the specific risk that your company faces must be assessed on a case by case basis, my advice from engineer to engineer is to follow certain best practices regardless of individual risk assessment.

Open source is not “free.” It doesn’t cost money to acquire, but using it has consequences. It is only free under the terms of its license.

Every open-source software has a license that tells you how to use it compliantly. This license grants you specific rights and requires you to comply with particular obligations if you use the software.

Permissive or copyleft

You might have heard of permissive or copyleft licenses.

Permissive licenses are flexible, allowing you to use or modify the software without sharing the changes in proprietary projects. Most require you to retain their original copyright notice when distributing derivative works. Typical permissive licenses are MIT, Apache 2.0, and BSD licenses.

On the other hand, copyleft licenses require that if you distribute software based on their code, you must open source your entire project under the same terms. For a company with proprietary software, this can be a nightmare. Typical copyleft licenses are MPL, AGPL, and GPL licenses.

If you’re unsure which rights you have to an NPM package and which obligations you must comply with when using it, you can use tldrlegal.com to learn the basics.

A tale of OSS license compliance

You would only cook a meal if you knew the ingredients, so why would you ship JavaScript code without knowing your dependencies and their licenses?

Building an SBOM

To better understand, you start building a detailed inventory of all open-source software used in your codebase, including dependencies of a dependency of a dependency. You think of it as your project’s ingredient list.

You start by accumulating data from the package.json “name,” “version,” and “license” fields of all your dependencies. To verify that the “license” field defines a valid license, you check it against the license identifiers of the SPDX list.

A few hours later, you learn that an ingredient list like that is called a software Bill of Materials (SBOM). It is essentially a list of all your project’s open-source packages that includes their versions and the licenses that govern them. You learn that besides analyzing a project’s open-source dependencies, SBOMs are also used for security purposes. You’re on the right track!

You also discover that something similar to what you’ve built is available directly on NPM—open source to manage your open source! You install it and use it instead of your hand-rolled solution. You realize it provides an abstraction for different ways to provide license information in package.json,so that you even get license identifiers for ancient packages.

Now that you know how to build an SBOM and have automated the process, you create one for every release and hand it to your legal department so they can check the licenses and their obligations before you make a new version of your software available to your customers.

However, occasionally, your colleagues in suits tell you that you need to rip out one or more dependencies before you can ship the new version of your software because the company is not ready to license all of the internal APIs of your SaaS offering as GPL and make their source code publicly available just yet. You hear that they’re also struggling to assess WTFPL and Beerware.

You shrug and spend a few days ripping out pieces of your software that you spent weeks building upon. After all, it’s a legal problem, so you cannot do much!

Trying to simplify the process

After doing this a few times, you notice that embedding feedback from your legal department into your release process and occasionally ripping out dependencies significantly slows down your development and release processes.

You sit together with your team and try to simplify that process. Digging deeper into the OSS licensing rabbit hole, you realize that:

  • If your legal department has approved a license for use in a project, it’s likely to be accepted again for a different project.
  • Most OSS license obligations only take effect when distributing software or derivative work.
  • Development dependencies make up most of your dependencies and most of the licenses your legal department denied.
  • You don’t distribute development dependencies in the Docker images of your APIs nor in your UI build artifacts.

Equipped with this knowledge, you want to use a license identifier allowlist as part of your pipelines and fail on unlisted licenses. Doing so will minimize the amount of implementation effort wasted because you’ll immediately get a warning about anything suspicious! You also plan on scanning dependencies and omitting devDependencies to reduce the number of packages you need to monitor.

Unfortunately, you can’t be sure that different dependency types in your dependencies’ package.json files are adequately separated. You don’t know how to handle peerDependencies, optionalDependencies and bundleDependencies. You realize you’re in way over your head and keep your previous approach because Pandora’s box is now open, and your colleagues from legal want you to ship software only after checking the licenses.

Managing license compliance pragmatically

I don’t know about you; but this reads like a horror tale to me! Being open-source license compliant is essential, but it shouldn’t stand in your way!

Using allowlists

The allowlist approach is good - you can start with a list of permissive license identifiers found most often on NPM packages. The following is a good starting point for both backend and frontend projects:

const allowedLicenses = [
  'MIT',
  'ISC',
  'Apache-2.0',
  '0BSD',
  'BSD-2-Clause',
  'BSD-3-Clause',
];

Frontend code

JavaScript in web pages is typically considered distributed code. It’s distributed to whoever loads the page in their browser. If you follow that reasoning, you have to comply with any obligations that affect distribution.

If you encounter a copyleft license, you might be obliged to open-source your codebase. If you only allow permissive licenses, you must at least provide a list of all open-source dependencies with their respective versions and licenses. To give you an example, part of the MIT license reads:

“The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.”

MIT License

To summarize, you should:

  1. Check against a permissive license identifier allowlist.
  2. Build an SBOM of all runtime dependencies shipped to the user’s browsers, listing at least their names, versions, and licenses.
  3. Display the SBOM to your frontend users (typically visible on an “About” page or where you show the current site or application version).

Scanning the right packages

As long as you’re using restrictive allowlists that focus on permissive licenses, it’s good enough to check only the licenses of packages used at runtime.

If you’re bundling your frontend code, don’t check dependencies based on package.json or lock files! Your bundler has dead code elimination (in a JavaScript context, this is typically called tree-shaking) and will limit the resulting code to the absolute minimum. The only way to know which packages you distribute is by integrating the SBOM generation with your bundler.

Building an SBOM of your runtime dependencies for Webpack, Rollup, or Vite is as simple as adding a plugin. As the author of webpack-license-plugin and rollup-license-plugin (also works for Vite!), I’ve got you covered!

Both plugins have a similar API and full test coverage. The Webpack plugin is compatible with and tested against Webpack major versions 2.x to 5.x. Here’s a simple example of how to set it up for vite:

import { createViteLicensePlugin } from 'rollup-license-plugin';
import { defineConfig } from 'vite';

const licensePlugin = createViteLicensePlugin({
  unacceptableLicenseTest(licenseIdentifier) {
    const allowedLicenses = [
      '0BSD',
      'ISC',
      'MIT',
      'Apache-2.0',
      'BSD-2-Clause',
      'BSD-3-Clause',
    ];

    return !allowedLicenses.includes(licenseIdentifier);
  },
});

export default defineConfig(() => {
  return {
    /* more config */
    plugins: [/* more plugins */ licensePlugin],
  };
});

With this setup, your production build will additionally generate an oss-licenses.json that contains an array of packages that are partially or entirely included in your build output. It will list the package name, version, author, repository, source tarball URL on the npm registry, license identifier, and license text.

Backend code

Backend code running on your infrastructure as part of a service and communicating JSON, XML, or other data payloads over HTTP, WebSockets, or similar protocols is not considered distribution, unless a license explicitly specifies otherwise.

Most notable in this regard is AGPL, which considers “interacting with it remotely through a computer network” a form of distribution.

If you’re not distributing your backend but host it on your infrastructure, you don’t have to deal with most distribution-related obligations and should:

  1. Check against a permissive license identifier allowlist.
  2. If you absolutely must and know the consequences, include some copyleft licenses (probably not AGPL). Make sure these are compatible!

Most people don’t bundle their Node code because it’s less valuable than doing so for frontends. However, to benefit from the simplicity of one of the SBOM-generating bundler plugins, you have to do that. Bundling backends has other benefits, like smaller bundle/container size and faster bootstraps, but also drawbacks, like requiring source maps for readable stack traces.

Proactively managing your open-source licenses can keep your codebase clean and litigation risk low. If you have a legal department, include them in the process!

Have a look at the webpack-license-plugin or rollup-license-plugin documentations. Start creating SBOMs as part of your build process and have them break your builds when necessary. It’s better than breaking the law!

Let me know if you have any questions or feedback. I’m always happy to chat!


Related topics

About the author

Photo of me

Software engineer building for the web since 1996. User group organiser, open source maintainer and Director UI Engineering at smartclip.

  1. LinkedIn
  2. GitHub
  3. X
  4. Threads