Dependency licenses

Last updated: Aug 11, 2026


Rationale

Every third-party dependency we take, in any component and any language, must be distributed under one of the licenses below. The list is expressed as SPDX identifiers, so it is unambiguous and machine-checkable rather than a matter of interpretation.

  • Public-domain equivalent: 0BSD, CC0-1.0, MIT-0, Unlicense.
  • Permissive: Apache-2.0, Apache-2.0 WITH LLVM-exception, BSD-2-Clause, BSD-3-Clause, BSL-1.0, ISC, MIT, NCSA, Unicode-3.0, Unicode-DFS-2016, Zlib.
  • Permissive, for data sets: CDLA-Permissive-2.0.
  • File-level copyleft: MPL-2.0.

Seventeen identifiers in total. The canonical machine-readable copy lives in common/utils/rust/checks/canonical/deny.toml, and this page is the human-readable statement of the same policy.

The reason we accept exactly these is that we ship proprietary software. Our components are delivered to customers as hosted services and as compiled binaries, so we can only depend on code whose license makes no claim on the code we link it against. Each accepted category clears that bar:

  • Public-domain equivalent and permissive licenses ask for attribution and nothing more. They impose no obligation on our own source.
  • MPL-2.0 is the one reciprocal license we accept. Its obligations attach per file: modifying an MPL-2.0 file means publishing that file, and combining it with our code leaves our files untouched. That boundary is compatible with proprietary distribution, and it is the license we publish our own Python packages under (signals, common/fluidattacks-core, tracks/sdk/python), so accepting it is consistent with how we license our own work.

Three things are therefore excluded:

  • Strong copyleft (GPL-2.0, GPL-3.0, AGPL-3.0). Reciprocity extends to the whole derived work, which would require publishing the components that link it. AGPL-3.0 reaches further still, triggering on network use rather than distribution, which is precisely how we deliver the platform.
  • Linking-level copyleft (LGPL-2.1, LGPL-3.0). The exemption these grant depends on the dependency remaining replaceable at link time. Our binaries are statically linked, so keeping that exemption would mean shipping the object files a customer needs to relink against a modified version, which we do not do. The practical effect is closer to the GPL than to a permissive license.
  • Proprietary, source-available, and unlicensed code. Anything under a bespoke commercial license, a source-available license such as the Business Source License, or no license at all, where the default is that no rights are granted.

This is the dependency-level counterpart to the preference for open source stated in our engineering philosophy: we consume open source broadly, but only where its terms leave our own licensing decisions to us.

Alternatives

Below are the other policies we considered, ordered from most to least interesting based on our specific needs.

Accepting strong copyleft with isolation rules

Allowing GPL and AGPL dependencies, subject to a rule that they may only be invoked as separate processes or reached over a network boundary.

  • It widens the pool of usable libraries, and some categories of tooling are predominantly GPL-licensed.
  • Whether a given integration creates a derived work is a legal judgement, not a mechanical check, so no CI job can decide it and every case becomes a review.
  • The isolation that makes it defensible is invisible in the dependency manifest. A dependency added as a library today can be linked directly tomorrow with nothing to flag the change.
  • AGPL-3.0 triggers on network use, which is how most of our components are delivered, so the process boundary that neutralizes the GPL does not help.
  • The obligation lands on the wrong desk. Engineering would carry a question only a legal review can settle, on the timescale of a dependency bump.

Reviewing each dependency case by case

Keeping no list, and asking for a human decision whenever a dependency is added.

  • It is maximally flexible and handles unusual licenses gracefully.
  • It scales with the number of dependencies rather than the number of distinct licenses, and transitive dependencies vastly outnumber direct ones.
  • Decisions are not recorded anywhere a future reader can find, so the same license gets re-litigated and different components drift to different answers.
  • It makes the safe path slower than the unsafe one, which is the wrong incentive for a check that exists to prevent an irreversible mistake.

A per-component allow list

Letting each component declare the licenses it accepts, on the grounds that a customer-installed CLI and an internal service carry different exposure.

  • It permits a genuinely riskier dependency in a component where the obligation would never trigger.
  • The distinction it relies on is unstable. Internal tools become customer-facing, and libraries move between components, so a dependency vetted under one component's rules silently inherits another's exposure.
  • It multiplies the review surface by the number of components without changing the underlying legal question.
  • We already treat shared configuration as canonical and diff every component against it, so a per-component list would be the only place where components are allowed to disagree on a matter of law.

Usage

The policy applies to every component. Enforcement does not yet, and the gap is worth being explicit about:

EcosystemGateStatus
Rustcargo-denyEnforced
TypeScriptpnpm licenses listNot enforced
Pythonpip-licensesNot enforced

In Rust, cargo-deny checks the resolved dependency graph against [licenses].allow on every lint run, and the list is not merely available but uniform: each component commits its own deny.toml, and the conformance check diffs that file against the canonical one, so a component cannot quietly widen the policy for itself.

The other two rows name a capability rather than a running check. pnpm licenses list reports the licenses of an installed tree but has no notion of a policy, so the comparison would be ours to write. pip-licenses can fail a build directly through --allow-only.

Both are weaker than the Rust gate, because the metadata upstream is weaker. Crates.io validates the license field of every published crate as an SPDX expression, which is what lets cargo-deny evaluate compound terms such as Apache-2.0 OR MIT. Neither of the other registries guarantees that: npm treats the license field as a convention, and in Python PEP 639 standardized an SPDX License-Expression field that remains optional, with free-text values and classifiers still permitted. A gate in those ecosystems compares license names rather than evaluating expressions, so it needs a normalization step that Rust gets from the registry for free.

Requesting an exception

A dependency whose license is not on the list has two paths, and they are not equivalent:

  • A single dependency needs it. Grant it narrowly. In Rust this is a [licenses].exceptions entry naming the one crate, which leaves the shared list untouched.
  • The license itself should be acceptable. Amend the canonical list, which means updating this page first. The change then reaches every Rust component at once through the conformance check, so it is a policy decision rather than a local one and follows the ADR process described in the Stack overview.

On this page