0038. Dependency license policy: a tripwire on the CNCF allowlist for pull requests, a narrower allowlist for releases
Context
ADR-0018 licenses the project under Apache-2.0 so that it stays acceptable to a foundation
(ADR-0028), and says that the licenses of the dependencies are tracked
separately. ADR-0030 generates the notices that the binaries owe their
dependencies. The check behind both is ci/check-release-licenses.sh: it runs go-licenses over the release builds and fails when
a linked Go package carries a license outside an allowlist of six (Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, MIT, Zlib), unless
ci/license-overrides.txt records an exception that someone checked by reading the license file.
That check is binding and runs for every pull request in the gate go, but it sees only the Go packages that a release build links. A
pull request that adds an npm package of the web UI, a Python tool or a Go tool module under a license that the CNCF would not admit
passes it, and the problem is found by a reader of the lockfile diff. The release does not catch it either for the npm packages that the
web UI bundle carries into myriarchd (the embedui build): the license report that Vite writes (build.license in
web/vite.config.ts) feeds THIRD_PARTY_NOTICES, and the generator needs a license identifier and a license text for every bundled
package but compares the identifier with no list. One bundled package already carries a license that is not on the release allowlist:
tslib 2.8.1 (0BSD, listed in THIRD_PARTY_NOTICES as used by myriarchd (web UI)). The CNCF publishes the licenses that a project may
use for third-party components without a review by its Governing Board (the CNCF Allowlist License Policy, adopted on 2018-05-01); a
dependency outside that list is a problem for a project on the path to a foundation, and it is cheaper to notice it in the pull request.
GitHub offers the comparison as a service: the dependency graph knows the manifests and lockfiles of the pull request and of its
base, and actions/dependency-review-action (a GitHub-owned action, so it fits rule R1 of ci/check-workflows.py) reports the
license of each dependency that the change adds or updates (from the license data of ClearlyDefined) and its known advisories. It
supports pull requests and merge groups, takes an allowlist of SPDX identifiers and a list of package URLs that are exempt from the
license check, and ignores the version in such a package URL.
The numbers that decide how long the exception list is were read from the dependency graph of the repository on 2026-10-15: 2,253 entries (1,223 Go modules of the product and of the 15 CI tool modules, 1,003 npm packages of the web UI, the website and the brand generator, 15 Python packages, 11 GitHub Actions and the repository itself). 81 packages carry a license expression that the CNCF allowlist and Apache-2.0 do not cover, and 18 have no license information. Of the 81, four are Go modules of the product (three linked into the release builds, one into the development build), 23 are Go modules of the CI tools, 4 are Python packages of tools, and 50 are npm packages: the trees of the development tools of the web UI and of the website, the brand generator, and packages of the website and the web UI with combined permissive licenses.
Decision
- One source file, two lists.
ci/license-allowlist.txtnames every license that dependencies may carry, each with the list it belongs to. The listrelease(Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, MIT and Zlib) is the binding allowlist of the Go packages that the release builds link (decision 6 says what it does not cover);ci/check-release-licenses.shreads it from the file instead of carrying it in the script. The listreviewholds the rest of the CNCF allowlist (0BSD, BSD-2-Clause-FreeBSD, MIT-0, OpenSSL, OpenSSL-standalone, PSF-2.0, Python-2.0, Python-2.0.1, PostgreSQL, SSLeay-standalone, UPL-1.0, X11 and the Google patent license for Golang, under the identifierLicenseRef-scancode-google-patent-license-golangthat the dependency graph uses for the PATENTS file of the Go modules). Apache-2.0 is the license of the project and needs no review under the CNCF policy; it is on the listrelease. Theallow-licensesof the dependency review are all the identifiers of the file, andci/tests/license-allowlist-test.shfails when the two differ. - The dependency review of a pull request is a tripwire, and the release check stays binding. The job
dependency-reviewofci.ymlis part ofci-ok. For a pull request and for a merge group it runsactions/dependency-review-action, pinned to a commit like every action, with the configuration.github/dependency-review-config.yml,fail-on-severity: highandfail-on-scopes: runtime. It fails when a dependency that the change adds or updates carries a license outside the list or a known vulnerability of high or critical severity in the runtime scope. The review checks what the dependency graph of GitHub reports for the change with the scope runtime; which ecosystems (Go, npm, Python, GitHub Actions) that covers follows the dependency graph and is to be confirmed by the first runs. For every other event (a push tomain, the nightly run, a manual run) a step says that there is nothing to compare, so the job succeeds instead of being skipped, because a skipped job failsci-ok. A license that only the listreviewallows passes the pull request and, for a Go package that a release build links, fails the release license check until someone decides about it: the decision to widen the listreleaseis a new decision record; a single package gets an entry inci/license-overrides.txt. The release check does not see the npm packages of the web UI bundle (decision 6). - The job holds the read-only token and asks no third party. It has no
permissionskey of its own and no secret; it writes no comment to the pull request (comment-summary-in-pr: never; the result is in the log and in the job summary); and it switches off the OpenSSF Scorecard lookup of the action (show-openssf-scorecard: false), which would send the repositories and names of the added packages to two other services (api.securityscorecards.dev and api.deps.dev). A dependency whose license is unknown is listed, not failed, as the action does; a license expression that is not valid SPDX fails. - Exceptions are recorded with a reason.
allow-dependencies-licenseslists packages that may carry any license in the dependency review, as package URLs without a version, each group under a comment that gives the reason; the test refuses an entry with a version, a repeated entry and a group without a reason. The reason names the license expression that the dependency graph reports for the package (the SBOM of the repository), because that expression decides whether an exception is needed:A AND Bneeds both identifiers on the list,A OR Bone of them, and aLicenseRefidentifier of the graph’s own, such asLicenseRef-bad-bsd-2-clause-isc, is on no list. An entry goes when the graph reports an expression that the list covers for every version of the package that a lockfile resolves. The initial list holds the 80 packages that the review would otherwise report today: the four dependencies of the product thatci/license-overrides.txtor the development build already explain, the Go and Python modules of the CI tools (MPL-2.0 libraries of HashiCorp and Let’s Encrypt, modules with several licenses in their test data or fonts, the REUSE checker), the development tool trees of the web UI and the website, the brand generator and the packages with combined permissive licenses. Each of them was checked against the SBOM on 2026-10-15: for at least one version that a lockfile resolves, the graph reports an expression that the list does not cover. The action ignores the version of a package URL, so an exception also covers later versions of the package; whoever updates a package of the list looks at its license. The Go and Python modules of the CI tools are no part of any release artifact. A change of the lists, or of an exception, is a change of policy and is reviewed as one; the configuration is read from the checkout of the pull request, like every other file of the pipeline. elkjsis not excepted. The npm packageelkjs(EPL-2.0, a weak copyleft license) is a dependency of Mermaid, and the Mermaid chunks that the website serves include the ELK layout. The notices page of the website (website/data/third_party.yaml) lists Mermaid only. Whether the website owes a notice forelkjsis a question for the maintainer, not for a pipeline exception. The review fails when a change addselkjsor changes its resolved version (Mermaid 12.1.0 resolveselkjs0.9.3 today); a Mermaid update that keeps that version passes. So the first Mermaid update that moveselkjsto another version fails the review until the question is answered.- The binding list covers Go packages; the npm packages of the web UI bundle get the review only.
ci/check-release-licenses.shrunsgo-licenses, which reads Go packages. The npm packages that the web UI bundle carries intomyriarchdare checked for their license by the dependency review of the pull request alone, against all entries of the file, and the generator ofTHIRD_PARTY_NOTICESonly requires a license identifier and a license text for each of them. One bundled package carries a license of the listreviewtoday:tslib2.8.1 (0BSD) ships inmyriarchd. Whether the listreleaseshould bind the web UI bundle as well is not decided here. It would need a check of the license report that Vite writes against the entries of the listrelease, and a decision for 0BSD first: widen the listreleasein a new decision record, or record an exception fortslib. Until then, a license of the listreviewin the bundle passes the pull request and the release.
Consequences
Positive
- The review checks what the dependency graph of GitHub reports for the change with the scope runtime; which ecosystems (Go, npm, Python, GitHub Actions) that covers follows the dependency graph and is to be confirmed by the first runs. What it sees is checked against the license policy and the advisories of the GitHub Advisory Database in the pull request that makes the change, and not at the release.
- The two lists cannot drift: one file, one test. The release check is as strict as before.
- The check uses a GitHub-owned action with the read-only token; no secret, no write scope, no new third party.
- The exception list documents the license landscape of the tool trees and the reason that each package is acceptable.
Negative
- The license data comes from ClearlyDefined through the dependency graph. It can be wrong, partial or missing; a package without
license information is only listed. A compound expression (
A AND B) needs both parts on the list, so packages with a documentation license next to the code license need an exception, and so does a package for which the graph reports an identifier of its own (LicenseRef-bad-bsd-2-clause-iscforemirpasic/gods, whose LICENSE holds two licenses of the list). - The review looks at the dependencies that a change adds or updates, not at the whole tree, and an exception covers every later version of its package.
- The list of exceptions is long at the start (80 packages), and the dependency graph does not say which npm packages it counts as development tools; entries for development-only trees may turn out to be unnecessary, and a Dependabot pull request that touches a package without an entry fails until one is added. The first runs show which.
- The review sees only what the dependency graph reports with the scope runtime. Whether the graph reports the Go modules, the npm packages, the Python packages and the GitHub Actions of a change that way has not been observed: a dependency that it reports with another scope, or does not report, is not checked. The first pull requests that change a Go, npm or Python manifest or a workflow show it.
- The action reads the dependency graph of GitHub. When GitHub cannot return the data for a revision range (a merge group that is not processed yet, for example), the comparison is unavailable and the job fails with a message. Nothing runs it again: someone re-runs the job, and a failing merge group removes the pull request from the merge queue, so the pull request has to be queued again.
elkjsstays an open item (decision 5).- The binding list does not reach the npm packages of the web UI bundle: a license of the list
reviewin the bundle passes both checks, andtslib(0BSD) ships inmyriarchdtoday (decision 6).
Alternatives considered
- Only the release license check: binding, but Go-only and late. An npm or Python dependency is not covered at all.
- Binding the web UI bundle with the list
releasenow: a check of the license report that Vite writes would make the list binding for the npm packages of the bundle too, buttslib(0BSD) is bundled and not on that list. Widening the list or recording an exception is a license decision for the maintainer, in its own record (decision 6). - A deny list (
deny-licenses): a license that nobody thought of passes. The option is also deprecated in the action. - The release list as the allowlist of the review: it would also trip on licenses that the CNCF admits and that the tool trees
contain (Python-2.0 of
argparse, 0BSD, MIT-0). That makes the tripwire noisier without protecting a release, because the release check already holds that line. - Third-party license scanners as actions (FOSSA or Trivy, for example): not allowed by rule R1 of
ci/check-workflows.py, and each would add a supply-chain dependency with the token in reach. - The configuration from the base branch (
owner/repo/path@ref): a pull request could then not loosen the policy that checks it. The action would fetch the file through the API, which counts against a rate limit, and the other policy files of the pipeline (workflows, allowlists, scripts) come from the checkout of the pull request as well, so reading this one from the base would protect little. fail-on-scopes: runtime, development: reports every license and advisory in the lockfiles of the development tools, which is noise that a project of this size cannot clear.- Generating the YAML configuration from the source file: the action needs the file in the checkout, so it would have to be committed and its drift checked; a test that compares the two is less machinery than a generator.