diff --git a/.buildkite/pipeline.yaml b/.buildkite/pipeline.yaml index 027dc17e83f..4d2f0955d09 100644 --- a/.buildkite/pipeline.yaml +++ b/.buildkite/pipeline.yaml @@ -323,6 +323,13 @@ steps: agents: arch: "amd64" + - <<: *common + <<: *source_test + label: ":scales: Dependency license check" + command: make license-check + agents: + arch: "amd64" + # Basic unit tests. - <<: *common <<: *source_test diff --git a/Makefile b/Makefile index 23b68a16827..baa949cb703 100644 --- a/Makefile +++ b/Makefile @@ -77,6 +77,10 @@ query: ## Runs a bazel query. E.g. make query TARGETS=//test/... @$(call query,$(OPTIONS) $(TARGETS)) .PHONY: query +mod: ## Runs a bazel mod command. E.g. make mod TARGETS="deps --output json" + @$(call mod,$(OPTIONS) $(TARGETS)) +.PHONY: mod + sudo: ## Runs the given $(TARGETS) as per run, but using "sudo -E". E.g. make sudo TARGETS=test/root:root_test ARGS=-test.v @$(call sudo,$(TARGETS),$(ARGS)) .PHONY: sudo @@ -206,6 +210,10 @@ governance-check: governance-regen ## Checks that the files derived from governa (echo "Generated governance files are out of sync. Please run \`make governance-regen\`." >&2; exit 1) .PHONY: governance-check +license-check: ## Checks that tools/licensecheck/dependencies.yaml has an entry for every dependency. + @$(call run,//tools/licensecheck/main:licensecheck,--mode=verify) +.PHONY: license-check + ## ## Canonical build and test targets. ## diff --git a/governance/BUILD b/governance/BUILD index 0374e2dd192..93a9c14d737 100644 --- a/governance/BUILD +++ b/governance/BUILD @@ -2,3 +2,8 @@ package( default_applicable_licenses = ["//:license"], licenses = ["notice"], ) + +exports_files( + ["licensing.yaml"], + visibility = ["//tools/licensecheck:__pkg__"], +) diff --git a/governance/licensing.yaml b/governance/licensing.yaml new file mode 100644 index 00000000000..ad88484760c --- /dev/null +++ b/governance/licensing.yaml @@ -0,0 +1,54 @@ +# Dependency licensing policy, enforced by tools/licensecheck's tests against +# tools/licensecheck/dependencies.yaml. +# +# allowed_licenses lists the license identifiers that dependencies may use. +# exceptions lists dependencies that are allowed to use other licenses; each +# entry must match the dependency's current licenses exactly (string for one +# license, sorted list for several, as in dependencies.yaml) so that license +# changes resurface for review, and must explain why the exception is +# acceptable. +allowed_licenses: + - Apache-2.0 + - 0BSD + - BSD-2-Clause + - BSD-2-Clause-FreeBSD + - BSD-3-Clause + - MIT + - MIT-0 + - ISC + - OpenSSL + - OpenSSL-standalone + - PSF-2.0 + - Python-2.0 + - Python-2.0.1 + - PostgreSQL + - SSLeay-standalone + - UPL-1.0 + - X11 + - Zlib + - Google patent license for Golang +exceptions: + - dependency: google_root_pem + license: NOASSERTION + exception_rationale: >- + Not software: a bundle of CA root certificates from https://pki.goog, + installed as trust anchors in test images only and never part of + released gVisor binaries. + - dependency: grpc + license: + - Apache-2.0 + - BSD-3-Clause + - MPL-2.0 + exception_rationale: >- + gRPC is Apache-2.0; its LICENSE file additionally embeds notices for + bundled components, including the MPL-2.0 text covering Mozilla's CA + certificate data (etc/roots.pem). Used by Bazel build and test tooling + only; released gVisor binaries use the pure-Go google.golang.org/grpc. + - dependency: llvm-raw + license: + - Apache-2.0 WITH LLVM-exception + - MIT + exception_rationale: >- + LLVM is part of the benchmark toolchain only and is not distributed with + gVisor. The LLVM exception only relaxes Apache-2.0's requirements, and + the MIT match comes from third-party notices embedded in LICENSE.TXT. diff --git a/tools/bazel.mk b/tools/bazel.mk index f2f32e5bbe8..64236ad0fae 100644 --- a/tools/bazel.mk +++ b/tools/bazel.mk @@ -360,6 +360,7 @@ run = $(call header,RUN $(1) $(2)) && $(call build_paths,$(1),"$$0" $(2)) sudo = $(call header,SUDO $(1) $(2)) && $(call build_paths,$(1),sudo -E "$$0" $(2)) test = $(call header,TEST $(1)) && $(call wrapper,$(BAZEL) test --strip=never $(BAZEL_OPTIONS) $(TEST_OPTIONS) $(1)) query = $(call wrapper,$(BAZEL) query $(BAZEL_OPTIONS) $(1)) +mod = $(call wrapper,$(BAZEL) mod $(BASE_OPTIONS) $(BAZEL_OPTIONS) $(1)) clean: ## Cleans the bazel cache. @$(call clean) diff --git a/tools/licensecheck/BUILD b/tools/licensecheck/BUILD new file mode 100644 index 00000000000..24575a5febb --- /dev/null +++ b/tools/licensecheck/BUILD @@ -0,0 +1,31 @@ +load("//tools:defs.bzl", "go_library", "go_test") + +package( + default_applicable_licenses = ["//:license"], + licenses = ["notice"], +) + +go_library( + name = "licensecheck", + srcs = ["licensecheck.go"], + marshal = False, + stateify = False, + visibility = ["//tools/licensecheck:__subpackages__"], + deps = [ + "@in_gopkg_yaml_v3//:go_default_library", + "@org_golang_x_mod//modfile:go_default_library", + "@org_golang_x_mod//module:go_default_library", + "@org_golang_x_mod//semver:go_default_library", + ], +) + +go_test( + name = "licensecheck_test", + size = "small", + srcs = ["licensecheck_test.go"], + data = [ + "dependencies.yaml", + "//governance:licensing.yaml", + ], + library = ":licensecheck", +) diff --git a/tools/licensecheck/dependencies.yaml b/tools/licensecheck/dependencies.yaml new file mode 100644 index 00000000000..354424ae653 --- /dev/null +++ b/tools/licensecheck/dependencies.yaml @@ -0,0 +1,1104 @@ +# Licenses of gVisor's external dependencies, as declared in MODULE.bazel and +# go.mod. +# Regenerate with: make run TARGETS=//tools/licensecheck/main:licensecheck ARGS=--mode=fetch +# Check completeness with ARGS=--mode=verify. +# Entries whose license cannot be fetched automatically are maintained by hand and preserved +# by --mode=fetch, so you can hand-edit the file for these. +- dependency: abseil-cpp + version: https://github.com/abseil/abseil-cpp/releases/download/20250814.1/abseil-cpp-20250814.1.tar.gz + retrieved: "2026-08-27" + commit: d38452e1ee03523a208362186fd42248ff2609f6 + sha256: 1692f77d1739bacf3f94337188b78583cf09bab7e420d2dc6c5605a4f86785a1 + license: Apache-2.0 +- dependency: bazel_skylib + version: https://github.com/bazelbuild/bazel-skylib/releases/download/1.8.2/bazel-skylib-1.8.2.tar.gz + retrieved: "2026-08-27" + commit: bce8d7f8de2e48033e771f9ccdd721edf9df84e8 + sha256: 6e78f0e57de26801f6f564fa7c4a48dc8b36873e416257a92bbb0937eeac8446 + license: Apache-2.0 +- dependency: buildkite_pipeline_schema + version: https://raw.githubusercontent.com/buildkite/pipeline-schema/f7a0894074d194bcf19eec5411fec0528f7f4180/schema.json + retrieved: "2026-08-27" + commit: f7a0894074d194bcf19eec5411fec0528f7f4180 + sha256: 3369c58038b4d55c08928affafb653716eb1e7b3cabb4a391aef979dd921f4e1 + license: MIT +- dependency: cel-spec + version: https://github.com/google/cel-spec/archive/refs/tags/v0.25.1.tar.gz + retrieved: "2026-08-27" + commit: 7f3c4c513b42d471d0be9439bf5bde38f45f8404 + sha256: 13583c5a312861648449845b709722676a3c9b43396b6b8e9cbe4538feb74ad2 + license: Apache-2.0 +- dependency: cel.dev/expr + version: v0.25.1 + retrieved: "2026-08-27" + commit: 7f3c4c513b42d471d0be9439bf5bde38f45f8404 + sha256: 12652bea2461aa29004d23052d3a08cb5911ab0a652469ef5da6efb37c12ee87 + license: Apache-2.0 +- dependency: cloud.google.com/go + version: v0.123.0 + retrieved: "2026-08-27" + commit: 4e8373586a5e48c18fbfd4bb0a3e259184e49a91 + sha256: e9c9509cf75beb9516795eeb113d01a20e2a71496a66ed736b6ed2a516878d52 + license: Apache-2.0 +- dependency: cloud.google.com/go/auth + version: v0.20.0 + retrieved: "2026-08-27" + commit: 9886dcff5240f37ff21e26c7462325f3c1cfafc8 + sha256: 06371584193bbb88a8f2c2931d85efaf45666cc52643be5b8aada6b053672f3a + license: Apache-2.0 +- dependency: cloud.google.com/go/auth/oauth2adapt + version: v0.2.8 + retrieved: "2026-08-27" + commit: 7e600b964c1cae3ba47fc7d1d340a536723d18cb + sha256: 4cd64264bafc4dd05f645452d6e1485adf92309130bd82b4c595db10cd534963 + license: Apache-2.0 +- dependency: cloud.google.com/go/bigquery + version: v1.44.0 + retrieved: "2026-08-27" + commit: 83f12d52faf6e642fd000c382d45a39915ba9cd5 + sha256: 8e51f2435212440dd7de4aac1064a9e14f197a0f6ded53fe5e4c87e5cadfb1e8 + license: Apache-2.0 +- dependency: cloud.google.com/go/compute/metadata + version: v0.9.0 + retrieved: "2026-08-27" + commit: 7b2365446764b0bba37198a55644954b437bddd0 + sha256: f97e35d1c96239b8574054a77e312c95d2c82a89c21daf46ffeaea6d5fc59eca + license: Apache-2.0 +- dependency: cloud.google.com/go/container + version: v1.40.0 + retrieved: "2026-08-27" + commit: e992f090fd6a4a0d576b960a3c0590f73a71e3e7 + sha256: 646de8d32751e7e90d738382be1f163287239f5282855f9229b10a0dca87ebc5 + license: Apache-2.0 +- dependency: cloud.google.com/go/iam + version: v1.7.0 + retrieved: "2026-08-27" + commit: 54d941a9a3f5177b37915f89084e4ba7ed3dae6a + sha256: a3d9f0ebc3baf0cda13db7be112508b19c3b8dee58c2176997a8aadd50ecc87a + license: Apache-2.0 +- dependency: cloud.google.com/go/monitoring + version: v1.24.3 + retrieved: "2026-08-27" + commit: bf00b9db931a52688d14eb44d4fdf4443338dc9a + sha256: e28a4ca46eef2ade6df229ee71065993a56673584e5f69a2ecf7d90e0e72fa8a + license: Apache-2.0 +- dependency: cloud.google.com/go/storage + version: v1.62.1 + retrieved: "2026-08-27" + commit: f4b10eef3a7b114033d52c8d83ad43b105a150b3 + sha256: 2c8a2520ef58c3d2a6e9e0392ec2cea37adb17db4e00c903d48415fd809397f8 + license: Apache-2.0 +- dependency: gazelle + version: https://github.com/bazel-contrib/bazel-gazelle/releases/download/v0.47.0/bazel-gazelle-v0.47.0.tar.gz + retrieved: "2026-08-27" + commit: b13c1573c378bb3dd9b9de1397db89d0392f8e72 + sha256: 675114d8b433d0a9f54d81171833be96ebc4113115664b791e6f204d58e93446 + license: Apache-2.0 +- dependency: github.com/BurntSushi/toml + version: v1.4.1-0.20240526193622-a339e1f7089c + retrieved: "2026-08-27" + commit: a339e1f7089ced06bae1eaf374cdfb950e92d2e8 + sha256: f15f0ca7a3c5a4275d3d560236f178e9d735a084534bf3b685ec5f676806230a + license: MIT +- dependency: github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp + version: v1.31.0 + retrieved: "2026-08-27" + commit: 316927bd0033e82948538685629172bcc6213562 + sha256: 60558bb0633e963a442aaf8492162e7bc2a7817fa7b5a07ffe83ebd2ee8463c2 + license: Apache-2.0 +- dependency: github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric + version: v0.55.0 + retrieved: "2026-08-27" + commit: 316927bd0033e82948538685629172bcc6213562 + sha256: f76ca1fa726654948a0ac1035bde302c370b07830c5c1549f66d2f11c8a88ee9 + license: Apache-2.0 +- dependency: github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping + version: v0.55.0 + retrieved: "2026-08-27" + commit: 316927bd0033e82948538685629172bcc6213562 + sha256: d7f5f7dd65f511941db0dd77c37701ee2cab3540f96896399194f786f5317e5e + license: Apache-2.0 +- dependency: github.com/Microsoft/go-winio + version: v0.6.2 + retrieved: "2026-08-27" + commit: 3c9576c9346a1892dee136329e7e15309e82fb4f + sha256: cd007bdb877a578efde2780bf5dd2f57dd557b5b08c3614597b820d0369ebb0f + license: MIT +- dependency: github.com/Microsoft/hcsshim + version: v0.13.0 + retrieved: "2026-08-27" + commit: 7084bd2fa445d83629a67a91ff4e072517a11f04 + sha256: 77c127afa2d239d7163c969642fa6015f8d0236672394e3304e5ab41dd8d706c + license: MIT +- dependency: github.com/cenkalti/backoff + version: v2.2.1+incompatible + retrieved: "2026-08-27" + commit: 5267b6dd4d2666b980a911bf235efa276222cbe2 + sha256: f8196815a1b4d25e5b8158029d5264801fc8aa5ff128ccf30752fd169693d43b + license: MIT +- dependency: github.com/cespare/xxhash/v2 + version: v2.3.0 + retrieved: "2026-08-27" + commit: 998dce232f17418a7a5721ecf87ca714025a3243 + sha256: 145a26cdc7c49db566017b807c4989ee7f7ddeb569423e9cb99f995fac3621d3 + license: MIT +- dependency: github.com/cilium/ebpf + version: v0.16.0 + retrieved: "2026-08-27" + commit: 061e86d8f5e99aa6f04d11a3807c8cacdbe7908b + sha256: 94f18289e55ece887579f7f3bd5117301c02a4413837fd6b5ff956962c8995cf + license: MIT +- dependency: github.com/cncf/xds/go + version: v0.0.0-20251210132809-ee656c7534f5 + retrieved: "2026-08-27" + commit: ee656c7534f5d7dc23d44dd611689568f72017a6 + sha256: cca4463a6b46441356c204b8580aed55a0df97e8289cfc04be7986cdf3a8011c + license: Apache-2.0 +- dependency: github.com/containerd/cgroups/v3 + version: v3.0.5 + retrieved: "2026-08-27" + commit: bce3c7e5fbf05852294998684293918fcb3f59dd + sha256: 8ddceaca7d2b60ed153643610d7c1620a3ef09a769eb2f1c7d3b058d46c1bd83 + license: Apache-2.0 +- dependency: github.com/containerd/console + version: v1.0.4 + retrieved: "2026-08-27" + commit: 8f6c4e4faef5a326d2cd907097d04c0239ee5e2f + sha256: 80c4b1f54a9d827803e9206923b3738baab47ea82e0e2d26b303056ea69d0cf2 + license: Apache-2.0 +- dependency: github.com/containerd/containerd/api + version: v1.10.0 + retrieved: "2026-08-27" + commit: 8b34ce391bd114e080892cccfa956ef3807c207c + sha256: 14852596630c8aa392efa6fd619c2c7248456cd6fbab972cadee51c73b67d015 + license: Apache-2.0 +- dependency: github.com/containerd/containerd/v2 + version: v2.1.5 + retrieved: "2026-08-27" + commit: fcd43222d6b07379a4be9786bda52438f0dd16a1 + sha256: dffe08911fcd926e4bc7dcd8b106775da61687dc1a3bfe68063721fda84e1cae + license: Apache-2.0 +- dependency: github.com/containerd/continuity + version: v0.4.5 + retrieved: "2026-08-27" + commit: 44e2adf7e9cd87330f3ad656e7a006ef91ed8c1e + sha256: dc5749a7ddf8e6a0e1cb7b59b2bb6421d9b0cd319d9e2811bed3df5f90c77d49 + license: Apache-2.0 +- dependency: github.com/containerd/errdefs + version: v1.0.0 + retrieved: "2026-08-27" + commit: 4817405e4a3caeb7aee9dac68ed55339c59cb635 + sha256: b4ef42c38d00f2c81cf3e69646549b7f5dc8fb72de9402d7b391963dc0ac9162 + license: Apache-2.0 +- dependency: github.com/containerd/errdefs/pkg + version: v0.3.0 + retrieved: "2026-08-27" + commit: 4817405e4a3caeb7aee9dac68ed55339c59cb635 + sha256: 593bf56f1991517e493510815d8fc6d344b8550fbfb3de56812f015096aab24f + license: Apache-2.0 +- dependency: github.com/containerd/fifo + version: v1.1.0 + retrieved: "2026-08-27" + commit: 151b205263c29d471e0d55c787d2ce9f5343fd46 + sha256: b139b81b236b29613edffca908d29753b9f49a4d2e3ea472e16162b4bfdcdbc2 + license: Apache-2.0 +- dependency: github.com/containerd/go-runc + version: v1.1.0 + retrieved: "2026-08-27" + commit: df3c22d088ca919d1943efa8ae0cfc0514da0542 + sha256: 2b096a08bc0a8c4fce5b57739cac528cc8fbfee993b0a4ddab82195c9aae3ef4 + license: Apache-2.0 +- dependency: github.com/containerd/log + version: v0.1.0 + retrieved: "2026-08-27" + commit: 0fc1e28871fdf2786e2cc51bbe4133db6547a199 + sha256: 2008faf206ec820e7fc3d40baba924936c21347dafad4a7ff122fa90e26e57d7 + license: Apache-2.0 +- dependency: github.com/containerd/plugin + version: v1.0.0 + retrieved: "2026-08-27" + commit: 19cedbf730bb9ae1c106c4d4df8492bb3d3fb8fd + sha256: 14f3b757a16b670ab7415ef2ce0e410054a1d0f7250a739a6aa881699a1aa065 + license: Apache-2.0 +- dependency: github.com/containerd/ttrpc + version: v1.2.7 + retrieved: "2026-08-27" + commit: 3b8c8b75577584e412043afb7b561193a4a47d2e + sha256: b5bf2d0282ccdeebe0ee6df737ee46e51fa661334a45bf82b91952eff6f8fcea + license: Apache-2.0 +- dependency: github.com/containerd/typeurl/v2 + version: v2.2.3 + retrieved: "2026-08-27" + commit: 5b047adad57d172afa9654c019fe65bf0540cc4d + sha256: aabdf9f00f4381f4bee966af8f7e2faed502b0571f701823d1c1ac0ca9a73189 + license: Apache-2.0 +- dependency: github.com/coreos/go-systemd/v22 + version: v22.6.0 + retrieved: "2026-08-27" + commit: 8214e15a7a799dc18f0ef8dd788b830f815d7530 + sha256: 7759d251cb9c2f020d8b7b5fa075653cb11cad4861e3e194bf3484267bbabc2f + license: Apache-2.0 +- dependency: github.com/creack/pty + version: v1.1.24 + retrieved: "2026-08-27" + commit: edfbf75025b0ba4ee17c19f52d9b600fad80a787 + sha256: 754e25253e76a5583b80d57d3add3afe68fc4d9f2a490968a9d1eda8c8fd8815 + license: MIT +- dependency: github.com/davecgh/go-spew + version: v1.1.2-0.20180830191138-d8f796af33cc + retrieved: "2026-08-27" + commit: d8f796af33cc + sha256: b4d0923b169b194f0016ec46f3df1ab0c68e27999743e43fe2de59ecb2484128 + license: ISC +- dependency: github.com/distribution/reference + version: v0.6.0 + retrieved: "2026-08-27" + commit: ff14fafe2236e51c2894ac07d4bdfc778e96d682 + sha256: 08232852a7a908263599777daab50e4306bd877e2467df036206367496122bcc + license: Apache-2.0 +- dependency: github.com/docker/docker + version: v28.1.1+incompatible + retrieved: "2026-08-27" + commit: 01f442b84d6a669c1e335b800d4670997cd5aa93 + sha256: d1dbfdd583636aeece256647e6567d50006d0d67480a273128d6ca6f79d90b3d + license: Apache-2.0 +- dependency: github.com/docker/go-connections + version: v0.4.0 + retrieved: "2026-08-27" + commit: 7395e3f8aa162843a74ed6d48e79627d9792ac55 + sha256: 570ebcee7e6fd844e00c89eeab2b1922081d6969df76078dfe4ffacd3db56ada + license: Apache-2.0 +- dependency: github.com/docker/go-units + version: v0.5.0 + retrieved: "2026-08-27" + commit: e682442797b36348f8e1f98defdbf32bac0b6c6f + sha256: 039d53ebe64af1aefa0be94ce42c621a17a3052c58ad15e5b3f357529beeaff6 + license: Apache-2.0 +- dependency: github.com/emicklei/go-restful/v3 + version: v3.11.0 + retrieved: "2026-08-27" + commit: 30bec7807481e62e1e1e59ad57e7f22054806966 + sha256: fc71c649398fa5d28ac7948d143bc8ac4803c01d24f852b9d50e87724ac8efc8 + license: MIT +- dependency: github.com/envoyproxy/go-control-plane/envoy + version: v1.36.0 + retrieved: "2026-08-27" + commit: 58535202ab441f4a081410fb8eac61cc35562b32 + sha256: dc628978da6002143e63a5bfe042c2b50e27bc3a40216f1d443548985f0f6b5a + license: Apache-2.0 +- dependency: github.com/envoyproxy/protoc-gen-validate + version: v1.3.0 + retrieved: "2026-08-27" + commit: 4eb9011f3e6d551d067d87c89f082261164fac31 + sha256: d91626b3a9a6b6a3b90dd85e92d922444f0a6c225c0caf9395f0abe49fad2aee + license: Apache-2.0 +- dependency: github.com/felixge/httpsnoop + version: v1.0.4 + retrieved: "2026-08-27" + commit: c5817c27ec125409c069052fdd171023c353501c + sha256: 75aa471311265e9860df0e523400b4650ed0c1a33262786a421f07226792e494 + license: MIT +- dependency: github.com/fxamacker/cbor/v2 + version: v2.7.0 + retrieved: "2026-08-27" + commit: 02b69dbb52f4ecf450b3aa5e9a04b7a0b4bf409a + sha256: b48771460405bab6896c85e21ebc5ff287d4018c6db5c8d822c55769c80dde03 + license: MIT +- dependency: github.com/go-echarts/go-echarts/v2 + version: v2.3.3 + retrieved: "2026-08-27" + commit: cd84fa303fad9f61cc4e3f8f445b72951224a8b1 + sha256: 3967e4665c04306ee8e8254b40d03a7ab565282083d02c67590a88cb59543993 + license: MIT +- dependency: github.com/go-jose/go-jose/v4 + version: v4.1.4 + retrieved: "2026-08-27" + commit: 0e59876635f3dbf46d7b5e97b52bb75a3f96e7d9 + sha256: 06a37b6668012a2dd109a5f71f6ae15387dc808b35d3cf73f5e406e78a17b027 + license: Apache-2.0 +- dependency: github.com/go-logr/logr + version: v1.4.3 + retrieved: "2026-08-27" + commit: 38a1c47ef633fa6b2eee6b8f2e1371ba8626e557 + sha256: 523d7a3587fe73f61ef41c8bc627f7d7f38de8104d46278154b307e60770ad70 + license: Apache-2.0 +- dependency: github.com/go-logr/stdr + version: v1.2.2 + retrieved: "2026-08-27" + commit: 521af2addfa7c81c8a65d0e85ed34bb6bb6dc262 + sha256: 9dd6893bf700198485ae699640b49bc1efbc6c73b37cb5792a0476e1fd8f7fef + license: Apache-2.0 +- dependency: github.com/go-openapi/jsonpointer + version: v0.21.0 + retrieved: "2026-08-27" + commit: 8b546b950409bd7b131488a88613339cd8937b7f + sha256: 8a36ba84f6f6148834875fff96da79fcc2b383ace69cc34d71756b3fa84b9952 + license: Apache-2.0 +- dependency: github.com/go-openapi/jsonreference + version: v0.20.2 + retrieved: "2026-08-27" + commit: 1f158e563669961b8e54817e3ea57978d439ffff + sha256: 27afd0bef56453e463eba6093afb04dc08d97b5ad0e15b2266cac867d062ae1b + license: Apache-2.0 +- dependency: github.com/go-openapi/swag + version: v0.23.0 + retrieved: "2026-08-27" + commit: 53e32e82f758c8e884819330a87aef294ff10c1f + sha256: a604425d0d3ff6936ffd056e86cd776c80e06874ef026e53da528208a986df94 + license: Apache-2.0 +- dependency: github.com/godbus/dbus/v5 + version: v5.1.0 + retrieved: "2026-08-27" + commit: e523abc905595cf17fb0001a7d77eaaddfaa216d + sha256: 03dfa8e71089a6f477310d15c4d3a036d82d028532881b50fee254358e782ad9 + license: BSD-2-Clause +- dependency: github.com/gofrs/flock + version: v0.8.0 + retrieved: "2026-08-27" + commit: 75ec202958fd1654ff47064ba3ebffe14443f391 + sha256: 816d8ac38372193f0de91a66666544df0a7363d6c003a36e7be632330081cba4 + license: BSD-3-Clause +- dependency: github.com/gogo/protobuf + version: v1.3.2 + retrieved: "2026-08-27" + commit: b03c65ea87cdc3521ede29f62fe3ce239267c1bc + sha256: dd2b73f163c8183941626360196c8f844addd95423d341a0412e1b22d0104ff7 + license: BSD-3-Clause +- dependency: github.com/golang/groupcache + version: v0.0.0-20210331224755-41bb18bfe9da + retrieved: "2026-08-27" + commit: 41bb18bfe9da + sha256: b27034e8fc013627543e1ad098cfc65329f2896df3da5cf3266cc9166f93f3a5 + license: Apache-2.0 +- dependency: github.com/golang/protobuf + version: v1.5.4 + retrieved: "2026-08-27" + commit: 75de7c059e36b64f01d0dd234ff2fff404ec3374 + sha256: 9a2f43d3eac8ceda506ebbeb4f229254b87235ce90346692a0e233614182190b + license: BSD-3-Clause +- dependency: github.com/google/btree + version: v1.1.2 + retrieved: "2026-08-27" + commit: 8e29150ba321eef204059de2ab494f179b6cff2c + sha256: faee8550c5fffb4ae1dadde5ccaccb13298726f9fad226bb4eed0c03c90a481d + license: Apache-2.0 +- dependency: github.com/google/gnostic-models + version: v0.6.8 + retrieved: "2026-08-27" + commit: 577692584cdf09674595bba0c182d0e8a44655d3 + sha256: 5276180bd184f64676867fc2f64a583175968c507d404be6b7f1261ead229484 + license: Apache-2.0 +- dependency: github.com/google/go-cmp + version: v0.7.0 + retrieved: "2026-08-27" + commit: 9b12f366a942ebc7254abc7f32ca05068b455fb7 + sha256: 64a9ce046f2c320e3783fba0d1f4a15f8a18f0b009b67bf27f7630919db3f539 + license: BSD-3-Clause +- dependency: github.com/google/go-github/v56 + version: v56.0.0 + retrieved: "2026-08-27" + commit: 5e25c5c215b3d21991d17447fba2e9d13a875159 + sha256: 8c86785cf5a02ddb48872399f3dcfb50b9b065225e62e5a59db512d9f9c4c443 + license: BSD-3-Clause +- dependency: github.com/google/go-querystring + version: v1.1.0 + retrieved: "2026-08-27" + commit: 1f4a1f9d6e29d314b2513651973fca5c4d4498f1 + sha256: a6aafc01f5602e6177928751074e325792a654e1d92f0e238b8e8739656dd72b + license: BSD-3-Clause +- dependency: github.com/google/gofuzz + version: v1.2.0 + retrieved: "2026-08-27" + commit: 379e164120fbc98885a8f494b5aa41ba94f64c56 + sha256: 5948f40af1923d8f98dc1d4191311030e40e0057fb255df19ebc0360f2faac16 + license: Apache-2.0 +- dependency: github.com/google/gopacket + version: v1.1.19 + retrieved: "2026-08-27" + commit: a9779d139771f6a06fc983b18e0efd23ca30222f + sha256: fc8cbe2239683199e677cb39f9303ae8d4a365f3c8cd8a2dcfa1f14c41bcff88 + license: BSD-3-Clause +- dependency: github.com/google/pprof + version: v0.0.0-20240711041743-f6c9dda6c6da + retrieved: "2026-08-27" + commit: f6c9dda6c6da638264f96f1097bce50fd82b4927 + sha256: 023f596ff6e6fa8aac6ee4b3855b2c677f149211f24d483df7c9697c05638363 + license: Apache-2.0 +- dependency: github.com/google/s2a-go + version: v0.1.9 + retrieved: "2026-08-27" + commit: b293be1aa7a6e6e4565f9967c093dd412253b267 + sha256: bf8f4b159d3fc201477822ed2514e4f05f58c764b78ffed73ec0accce5fb7f07 + license: Apache-2.0 +- dependency: github.com/google/subcommands + version: v1.0.2-0.20190508160503-636abe8753b8 + retrieved: "2026-08-27" + commit: 636abe8753b8 + sha256: 579d425640f38e875e155836d9b99b0265d551af34df8a17677f4e7930875fdc + license: Apache-2.0 +- dependency: github.com/google/uuid + version: v1.6.0 + retrieved: "2026-08-27" + commit: 0f11ee6918f41a04c201eceeadf612a377bc7fbc + sha256: d0f02f377217f42702e259684e06441edbf5140dddcc34ba9bea56038b38a6ed + license: BSD-3-Clause +- dependency: github.com/googleapis/enterprise-certificate-proxy + version: v0.3.14 + retrieved: "2026-08-27" + commit: 023385dce9a54844c04892165e1da8c4a2e514b0 + sha256: ab19ba449c062755072bf9beaa6596a48e3c5b68f916dae1b0a3ff7624f135e6 + license: Apache-2.0 +- dependency: github.com/googleapis/gax-go/v2 + version: v2.21.0 + retrieved: "2026-08-27" + commit: 774a802ed711be92679cbaaa77e6aab36fa2ab0c + sha256: e4ff14be567241c53c6df76712016f1cb38738bdbe97c74087ba099d8bb7e894 + license: BSD-3-Clause +- dependency: github.com/googleapis/gnostic + version: v0.5.5 + retrieved: "2026-08-27" + commit: 1550ae29653d42db69c8a98d368648be7f2c488d + sha256: 50fab68c592e8c8038b48b3c7b68d8f56297a58da28194ace2a43a9866c4025b + license: Apache-2.0 +- dependency: github.com/hanwen/go-fuse/v2 + version: v2.3.0 + retrieved: "2026-08-27" + commit: d537a5a8d65c6552bc3844571f1d8ec76bcac5e4 + sha256: 6ee5712414cdddc7e10e5d1ff059128879250d2d04cd27c99b076c55ef0b47bf + license: BSD-3-Clause +- dependency: github.com/ianlancetaylor/demangle + version: v0.0.0-20240912202439-0a2b6291aafd + retrieved: "2026-08-27" + commit: 0a2b6291aafdd313f7e82cf15f5544bfbfb00559 + sha256: a957c8d33a5e55e3d73011cb93842ce99a0f9524332eaf924b60bb44e632f3d1 + license: BSD-3-Clause +- dependency: github.com/imdario/mergo + version: v0.3.5 + retrieved: "2026-08-27" + commit: 9316a62528ac99aaecb4e47eadd6dc8aa6533d58 + sha256: 7e9a0a74ebfb0e2d1cde49fe5e7c8f54975b16601ceaa4a308887790e4f101dc + license: BSD-3-Clause +- dependency: github.com/josharian/intern + version: v1.0.0 + retrieved: "2026-08-27" + commit: 8e6ff32b3e7c0b018c43953085fe2ac330fe9acd + sha256: 5679bfd11c14adccdb45bd1a0f9cf4b445b95caeed6fb507ba96ecced11c248d + license: MIT +- dependency: github.com/json-iterator/go + version: v1.1.12 + retrieved: "2026-08-27" + commit: 024077e996b048517130b21ea6bf12aa23055d3d + sha256: d001ea57081afd0e378467c8f4a9b6a51259996bb8bb763f78107eaf12f99501 + license: MIT +- dependency: github.com/mailru/easyjson + version: v0.7.7 + retrieved: "2026-08-27" + commit: c120ca7ced6051261161ce15e8f1542a4b2567fc + sha256: 139387981a220d499c9f47cece42a2002f105e4ee3ab9c74188a7fb8a9be711e + license: MIT +- dependency: github.com/mattbaird/jsonpatch + version: v0.0.0-20171005235357-81af80346b1a + retrieved: "2026-08-27" + commit: 81af80346b1a + sha256: 55abaf4d26d8ad7f81c230f38a6e482b6b416d9b5777a6c3b1a5c140465a5235 + license: Apache-2.0 +- dependency: github.com/matttproud/golang_protobuf_extensions + version: v1.0.1 + retrieved: "2026-08-27" + commit: c12348ce28de40eed0136aa2b644d0ee0650e56c + sha256: e64dc58023f4b8c4472d05a44f2719b84d6c2cc364cc682820c9f72b233c9cdc + license: Apache-2.0 +- dependency: github.com/mdlayher/socket + version: v0.5.1 + retrieved: "2026-08-27" + commit: 9c51a391be6c6bc5b7ce52f328497931c5e97733 + sha256: f0f07bdd9811f8571c7864cf5e0f06d85cfb3edbb254d6094a31e7f1d427e2a2 + license: MIT +- dependency: github.com/mdlayher/vsock + version: v1.2.1 + retrieved: "2026-08-27" + commit: 8569e301791e164b669ff36b51035bb1d7c9b3a3 + sha256: cd25f9f33aa4fb945502351caeeed6994e1f34824cc826ca2daa1b21c45bc0d1 + license: MIT +- dependency: github.com/moby/docker-image-spec + version: v1.3.1 + retrieved: "2026-08-27" + commit: f1d00ebd2d6d6805170d5543dbca4b850f35f9af + sha256: e3fe212be410465c0cae032baa7e2c7282b9bb878f081f00549333edc260c306 + license: Apache-2.0 +- dependency: github.com/moby/sys/capability + version: v0.4.0 + retrieved: "2026-08-27" + commit: 50e999a770172a519119e1c4fd4b6153b7dac90d + sha256: 3502bba106a402fd440d1d4e8eb5ca741da4d70a674e8313725838ec8e22b44c + license: BSD-2-Clause +- dependency: github.com/moby/sys/mountinfo + version: v0.7.2 + retrieved: "2026-08-27" + commit: cafbe42351600ca9b363e220722f66d96f6e71f4 + sha256: 94f79687bc3b72ca4cc60fafda851454fefc0ef04bdb4499e8c7c2b2e231b5d3 + license: Apache-2.0 +- dependency: github.com/moby/sys/sequential + version: v0.6.0 + retrieved: "2026-08-27" + commit: cafbe42351600ca9b363e220722f66d96f6e71f4 + sha256: 7870e47447a7e81a360d5d323bb9d52aaa951cbe70c267bea46b19ff1ed48bab + license: Apache-2.0 +- dependency: github.com/moby/sys/signal + version: v0.7.1 + retrieved: "2026-08-27" + commit: cafbe42351600ca9b363e220722f66d96f6e71f4 + sha256: 8c3853bc4ba4f283f7fccde5c548e93c3613db46f5209394a60cdbbc3eaf4f42 + license: Apache-2.0 +- dependency: github.com/moby/sys/symlink + version: v0.3.0 + retrieved: "2026-08-27" + commit: cafbe42351600ca9b363e220722f66d96f6e71f4 + sha256: c0036c97c9b7b7a246468165a968fedd974691161b083f1e7e99bafc58e57451 + license: Apache-2.0 +- dependency: github.com/moby/sys/user + version: v0.4.0 + retrieved: "2026-08-27" + commit: 71f0c5ead442a11945a589a7aafd6ef5976fffc8 + sha256: 4b96521752263905b26acd41c982be7bfb73d45243d070707e3cdd57f9fb3a91 + license: Apache-2.0 +- dependency: github.com/moby/sys/userns + version: v0.1.0 + retrieved: "2026-08-27" + commit: 54475191138bd297c627eb1a59e1e54b953957f1 + sha256: 24c102bcc418dedc378fbcaa53b10251b35042f2e86bf088f773250064fe3080 + license: Apache-2.0 +- dependency: github.com/modern-go/concurrent + version: v0.0.0-20180306012644-bacd9c7ef1dd + retrieved: "2026-08-27" + commit: bacd9c7ef1dd + sha256: 91ef49599bec459869d94ff3dec128871ab66bd2dfa61041f1e1169f9b4a8073 + license: Apache-2.0 +- dependency: github.com/modern-go/reflect2 + version: v1.0.2 + retrieved: "2026-08-27" + commit: 2b33151c9bbc5231aea69b8861c540102b087070 + sha256: f46f41409c2e74293f82cfe6c70b5d582bff8ada0106a7d3ff5706520c50c21c + license: Apache-2.0 +- dependency: github.com/mohae/deepcopy + version: v0.0.0-20170308212314-bb9b5e7adda9 + retrieved: "2026-08-27" + commit: bb9b5e7adda9 + sha256: ba787dcf6444f09d620b827c18510b23bda9bf5385d56de2cc97f0fe919fe70e + license: MIT +- dependency: github.com/munnerz/goautoneg + version: v0.0.0-20191010083416-a7dc8b61c822 + retrieved: "2026-08-27" + commit: a7dc8b61c822 + sha256: 3d7ce17916779890be02ea6b3dd6345c3c30c1df502ad9d8b5b9b310e636afd9 + license: BSD-3-Clause +- dependency: github.com/opencontainers/go-digest + version: v1.0.0 + retrieved: "2026-08-27" + commit: ea51bea511f75cfa3ef6098cc253c5c3609b037a + sha256: 615efb31ff6cd71035b8aa38c3659d8b4da46f3cd92ac807cb50449adfe37c86 + license: Apache-2.0 +- dependency: github.com/opencontainers/image-spec + version: v1.1.1 + retrieved: "2026-08-27" + commit: 147f9c13cedb47a0c4d9a11a222961073d585877 + sha256: 3a32eeb459719172947c20be78b5b7293c6ea84f4039184e846f6b676bc19574 + license: Apache-2.0 +- dependency: github.com/opencontainers/runtime-spec + version: v1.2.1 + retrieved: "2026-08-27" + commit: 524fc0e1b8ab0180e2fc9abd31837a0f4ed1fd6b + sha256: 847dfbbec37aef7d1e14caebacb0ea036ecd22fa84bca96a0da3dfebfc16ef31 + license: Apache-2.0 +- dependency: github.com/pkg/errors + version: v0.9.1 + retrieved: "2026-08-27" + commit: 614d223910a179a466c1767a985424175c39b465 + sha256: d4c36b8bcd0616290a3913215e0f53b931bd6e00670596f2960df1b44af2bd07 + license: BSD-2-Clause +- dependency: github.com/planetscale/vtprotobuf + version: v0.6.1-0.20240319094008-0393e58bdf10 + retrieved: "2026-08-27" + commit: 0393e58bdf106fe0347e554d272a8f2c84d12461 + sha256: 6fdbac53c6ace58d732be8ec29c1aae928639eb12960cc4c52a9f7a25395ca05 + license: BSD-3-Clause +- dependency: github.com/prometheus/client_model + version: v0.3.0 + retrieved: "2026-08-27" + commit: 63fb9822ca3ba7a4ba5184071fb8f2ea000a99ef + sha256: 2a1d147754959287fc34a7bb7c333b3d6fe0ca0d7db1606c49e8f48fd0311547 + license: Apache-2.0 +- dependency: github.com/prometheus/common + version: v0.11.1 + retrieved: "2026-08-27" + commit: 217fd62d44a79db8f842113234ae9383b8374983 + sha256: b5922c5ceedb86debbb05a3c2c78616273c9f45f127dada540dbb1a61d7e0b02 + license: Apache-2.0 +- dependency: github.com/sirupsen/logrus + version: v1.9.3 + retrieved: "2026-08-27" + commit: d40e25cd45ed9c6b2b66e6b97573a0413e4c23bd + sha256: 4501f4e6b858bfdd997671fcdd2f647a3178b29b6b4d1344caa7c07517121dd0 + license: MIT +- dependency: github.com/spf13/pflag + version: v1.0.5 + retrieved: "2026-08-27" + commit: 2e9d26c8c37aae03e3f9d4e90b7116f5accb7cab + sha256: fc6e704f2f6a84ddcdce6de0404e5340fa20c8676181bf5d381b17888107ba84 + license: BSD-3-Clause +- dependency: github.com/spiffe/go-spiffe/v2 + version: v2.6.0 + retrieved: "2026-08-27" + commit: c6d0af3a4780fcc69132bb8bc5d2bdfcbe5ea42b + sha256: b90f378c70e2c985f61893ca20bf3e8258bb4f66920af759276d330513d0d7d6 + license: Apache-2.0 +- dependency: github.com/vishvananda/netlink + version: v1.3.1-0.20250303224720-0e7078ed04c8 + retrieved: "2026-08-27" + commit: 0e7078ed04c84cea47daea45be061544e565ec49 + sha256: e943364458eb30085be77fc1b9ef82e4b29bc9dc78078c0a08a7e80357cb83cf + license: Apache-2.0 +- dependency: github.com/vishvananda/netns + version: v0.0.5 + retrieved: "2026-08-27" + commit: 4c46424d73b556b3ea4bc5a7cec9e7376dcb2a73 + sha256: 07804b88c922c2a6780efe392ffef9d126eac584384eb3eb17bcadd34cfa9422 + license: Apache-2.0 +- dependency: github.com/x448/float16 + version: v0.8.4 + retrieved: "2026-08-27" + commit: cb9afec31f2649663ebb64da5c6c32c3d365c3ca + sha256: 73b24a41037ea999ab66851e3798a0973dbb1f214925915b01f0820f7b2f1500 + license: MIT +- dependency: github.com/xeipuuv/gojsonpointer + version: v0.0.0-20180127040702-4e3ac2762d5f + retrieved: "2026-08-27" + commit: 4e3ac2762d5f + sha256: 5b1a4bcc8e003f214c92b3fa52959d9eb0e3af1c0c529efa55815db951146e48 + license: Apache-2.0 +- dependency: github.com/xeipuuv/gojsonreference + version: v0.0.0-20180127040603-bd5ef7bd5415 + retrieved: "2026-08-27" + commit: bd5ef7bd5415 + sha256: 7ec98f4df894413f4dc58c8df330ca8b24ff425b05a8e1074c3028c99f7e45e7 + license: Apache-2.0 +- dependency: github.com/xeipuuv/gojsonschema + version: v1.2.0 + retrieved: "2026-08-27" + commit: 82fcdeb203eb6ab2a67d0a623d9c19e5e5a64927 + sha256: 55c8ce068257aa0d263aad7470113dafcd50f955ee754fc853c2fdcd31ad096f + license: Apache-2.0 +- dependency: github_workflow_schema + version: https://raw.githubusercontent.com/SchemaStore/schemastore/166136b96a14f103a948053903e9339e63ad9170/src/schemas/json/github-workflow.json + retrieved: "2026-08-27" + commit: 166136b96a14f103a948053903e9339e63ad9170 + sha256: 7499ccb3e75975504ea1ee7c70291e0c9f6c1f684678091d013061fe263e3ddb + license: Apache-2.0 +- dependency: go.opencensus.io + version: v0.24.0 + retrieved: "2026-08-27" + commit: b1a01ee95db0e690d91d7193d037447816fae4c5 + sha256: 203a767d7f8e7c1ebe5588220ad168d1e15b14ae70a636de7ca9a4a88a7e0d0c + license: Apache-2.0 +- dependency: go.opentelemetry.io/auto/sdk + version: v1.2.1 + retrieved: "2026-08-27" + commit: 715f58ce2f17e2176b8e53b871e47531a259cc1d + sha256: a08a16fc0a7041cdb875f47a260a56000632212c85f65a302a043c76380c8335 + license: Apache-2.0 +- dependency: go.opentelemetry.io/contrib/detectors/gcp + version: v1.39.0 + retrieved: "2026-08-27" + commit: 9a6a4d7dec6c950b12977cb166e1954bc74e8777 + sha256: 861471f5354793616a1bf86f40f6bf9e96dd85189e68c7d67cb72332a031b7bd + license: + - Apache-2.0 + - BSD-3-Clause +- dependency: go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc + version: v0.67.0 + retrieved: "2026-08-27" + commit: d8dabf67361a4619c353ad0637432f3d0d16ba63 + sha256: 8d28360aab471b397d81ea1e5e738fd99df97c3ba04b485e7924cd716d6c7cd1 + license: + - Apache-2.0 + - BSD-3-Clause +- dependency: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp + version: v0.67.0 + retrieved: "2026-08-27" + commit: d8dabf67361a4619c353ad0637432f3d0d16ba63 + sha256: a184e0b582e09004f01272b6f326e2a83b7a40694907fb7bbcd95080e33cc8e7 + license: + - Apache-2.0 + - BSD-3-Clause +- dependency: go.opentelemetry.io/otel + version: v1.43.0 + retrieved: "2026-08-27" + commit: 9276201a64b623606e3eaa0d61ae8ee6d62756c0 + sha256: 177d24c79e756950c431aa87c1c613bb807cc7fe435260b72c5f675b01651921 + license: + - Apache-2.0 + - BSD-3-Clause +- dependency: go.opentelemetry.io/otel/metric + version: v1.43.0 + retrieved: "2026-08-27" + commit: 9276201a64b623606e3eaa0d61ae8ee6d62756c0 + sha256: 3efb5ad706fab815f4b2e8fc89c1656ec8d2403f07edd406c17c2310dd8a08a6 + license: + - Apache-2.0 + - BSD-3-Clause +- dependency: go.opentelemetry.io/otel/sdk + version: v1.43.0 + retrieved: "2026-08-27" + commit: 9276201a64b623606e3eaa0d61ae8ee6d62756c0 + sha256: cf46abeb516a88a0eb33117103e39fb3161f0db91cd487a84748aae16938e8f2 + license: + - Apache-2.0 + - BSD-3-Clause +- dependency: go.opentelemetry.io/otel/sdk/metric + version: v1.43.0 + retrieved: "2026-08-27" + commit: 9276201a64b623606e3eaa0d61ae8ee6d62756c0 + sha256: b02c5dded25925d2becf8ea69d446f2eb786a576ab4a7dd8014f3afbd861fc49 + license: + - Apache-2.0 + - BSD-3-Clause +- dependency: go.opentelemetry.io/otel/trace + version: v1.43.0 + retrieved: "2026-08-27" + commit: 9276201a64b623606e3eaa0d61ae8ee6d62756c0 + sha256: 34fdc70a75e90d68e36caa59b9e800a5de5266118393eaee4489dd0abe3cb62d + license: + - Apache-2.0 + - BSD-3-Clause +- dependency: go.uber.org/atomic + version: v1.7.0 + retrieved: "2026-08-27" + commit: "" + sha256: 0417b4203eb1454a0eeda59e79f426c27fcfb9e3c74077060303429cc16db307 + license: MIT +- dependency: go.uber.org/multierr + version: v1.8.0 + retrieved: "2026-08-27" + commit: "" + sha256: 0077abedf9a4798c50e015045b5cc092b8001be6d994fc59781c4b0d6ae94507 + license: MIT +- dependency: golang.org/x/crypto + version: v0.49.0 + retrieved: "2026-08-27" + commit: 982eaa62dfb7273603b97fc1835561450096f3bd + sha256: cbca45d1fcb0fa359cd232b884ba86cfa1a32644ff7efdeb79807db3eb0f005d + license: BSD-3-Clause +- dependency: golang.org/x/exp + version: v0.0.0-20250711185948-6ae5c78190dc + retrieved: "2026-08-27" + commit: 6ae5c78190dcdcaea7a724ce5fd85346895b9e12 + sha256: 01d8fecd44e904b86e66c78cf6b403b27312513c0534455ee7b9c86600d9b7aa + license: BSD-3-Clause +- dependency: golang.org/x/exp/typeparams + version: v0.0.0-20221208152030-732eee02a75a + retrieved: "2026-08-27" + commit: 732eee02a75a571a8347fdf60af8c8b9e3ca9254 + sha256: 9bd73f186851c6229484f486981f608d16e2b86acbbef6f4f7cc0480a508a4a4 + license: BSD-3-Clause +- dependency: golang.org/x/mod + version: v0.34.0 + retrieved: "2026-08-27" + commit: 1ac721dff8591283e59aba6412a0eafc8b950d83 + sha256: 8e6b8d8a771dac070d44c8c701b1fba4c3565031f4f7170e6fed9d3022b4aa32 + license: BSD-3-Clause +- dependency: golang.org/x/net + version: v0.52.0 + retrieved: "2026-08-27" + commit: 316e20ce34d380337f7983808c26948232e16455 + sha256: 5b8bf13a81552cd4ffc06dae2b78fb1e72ece57e54446e396ff9c19251e917ed + license: BSD-3-Clause +- dependency: golang.org/x/oauth2 + version: v0.36.0 + retrieved: "2026-08-27" + commit: 4d954e69a88d9e1ccb8439f8d5b6cbef230c4ef9 + sha256: 15bf65ff103e5dafc809d78cd037a1d1e88ccb8451824e851156002a13304504 + license: BSD-3-Clause +- dependency: golang.org/x/sync + version: v0.20.0 + retrieved: "2026-08-27" + commit: ec11c4a93de22cde2abe2bf74d70791033c2464c + sha256: 7179d4d68800f6fdcadb9d4bbf11cbb5df9b40360c89305c203fe20723cbd375 + license: BSD-3-Clause +- dependency: golang.org/x/sys + version: v0.43.0 + retrieved: "2026-08-27" + commit: f33a730cd0c449cfd6f7106780c73052e96cc33d + sha256: cb8b073934cf7e579c9f80c4d12619d5f96fcf97af6f07fdc649e7acec0aaddf + license: BSD-3-Clause +- dependency: golang.org/x/telemetry + version: v0.0.0-20250908211612-aef8a434d053 + retrieved: "2026-08-27" + commit: aef8a434d053e5ae77d73c0f4dc37b10d36c39fa + sha256: ae079c5f1321c91facaf2e5aaaa23eeed74228446654ce41c1494b29760a9b4e + license: BSD-3-Clause +- dependency: golang.org/x/term + version: v0.42.0 + retrieved: "2026-08-27" + commit: 52b71d3344c86b384ed34ebf73f1e6f37044fe79 + sha256: c69cf2bf68d1d73e15a31c1c9a687316edef5c4ef9b6bb35aaedc08c6204f3c2 + license: BSD-3-Clause +- dependency: golang.org/x/text + version: v0.36.0 + retrieved: "2026-08-27" + commit: 8577a70117e110160c45f32af0e0df84eef844f7 + sha256: 15c60227cf084605a0256b8eacd9cfaf411109fe80c9e68b14a9367a5e42b23c + license: BSD-3-Clause +- dependency: golang.org/x/time + version: v0.15.0 + retrieved: "2026-08-27" + commit: 812b343c8714c317b0dad633efa6d103e554c006 + sha256: 8cfb20e915fa5d6c3fd264304233f8d71a3385e48a37819239c532ad9da27f84 + license: BSD-3-Clause +- dependency: golang.org/x/tools + version: v0.43.0 + retrieved: "2026-08-27" + commit: 24a8e95f9d7ae2696f66314da5e50c0d98ccaa90 + sha256: 7c1f13a8ddbf2de1136e1170943754938c156ec7f921a24588d2ba70ca2c142b + license: BSD-3-Clause +- dependency: golang.org/x/xerrors + version: v0.0.0-20220907171357-04be3eba64a2 + retrieved: "2026-08-27" + commit: 04be3eba64a22a838cdb17b8dca15a52871c08b4 + sha256: b9c481db33c4b682ba8ba348018ddbd2155bd227cc38ff9f6b4cb2b74bbc3c14 + license: BSD-3-Clause +- dependency: google.golang.org/api + version: v0.274.0 + retrieved: "2026-08-27" + commit: 6c759a2bb66da9db49027475e4e76301b8d063df + sha256: 7d6d36f933d73c87eb2183f199bd94c6325d3f1ffad16d7d9dddbeeadb4bb50b + license: BSD-3-Clause +- dependency: google.golang.org/genproto + version: v0.0.0-20260319201613-d00831a3d3e7 + retrieved: "2026-08-27" + commit: d00831a3d3e7add50f9d3b35445a5e0497666e41 + sha256: a75d9f7ea0d1bba57acec769c848764c866c88f22fe8c0e9b295134d20e5400f + license: Apache-2.0 +- dependency: google.golang.org/genproto/googleapis/api + version: v0.0.0-20260401024825-9d38bb4040a9 + retrieved: "2026-08-27" + commit: 9d38bb4040a9551934095960a7e8521e58594918 + sha256: 88b3d97eaf60fbc3a67fb3bf0e190cf9a5dc9c8d2b6e3b9cbb80bceb004967bf + license: Apache-2.0 +- dependency: google.golang.org/genproto/googleapis/rpc + version: v0.0.0-20260401024825-9d38bb4040a9 + retrieved: "2026-08-27" + commit: 9d38bb4040a9551934095960a7e8521e58594918 + sha256: 782f7750728f860a0224e85186cf2696f58434d6d0556459c7e236cbcd8ce7c3 + license: Apache-2.0 +- dependency: google.golang.org/grpc + version: v1.80.0 + retrieved: "2026-08-27" + commit: 397e45edaa68f8763773bbaaf539cf7894169cd2 + sha256: 12b91cdce1bbcd837193ef71c42a09b454bff644e0ddab389a396328c2e41779 + license: Apache-2.0 +- dependency: google.golang.org/protobuf + version: v1.36.11 + retrieved: "2026-08-27" + commit: 96a179180f0ad6bba9b1e7b6e38d0affb0168e9a + sha256: 14983d36c56a814ed91b6d652f2b8f895baba1b84eb43b28a0b132c8637cd274 + license: BSD-3-Clause +- dependency: google_benchmark + version: https://github.com/google/benchmark/archive/refs/tags/v1.8.4.tar.gz + retrieved: "2026-08-27" + commit: a4cf155615c63e019ae549e31703bf367df5b471 + sha256: 3e7059b6b11fb1bbe28e33e02519398ca94c1818874ebed18e504dc6f709be45 + license: Apache-2.0 +- dependency: google_root_pem + version: https://pki.goog/roots.pem + retrieved: "2026-08-25" + commit: "" + sha256: "" + license: NOASSERTION +- dependency: googletest + version: https://github.com/google/googletest/releases/download/v1.17.0/googletest-1.17.0.tar.gz + retrieved: "2026-08-27" + commit: 52eb8108c5bdec04579160ae17225d66034bd723 + sha256: 65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c + license: BSD-3-Clause +- dependency: gopkg.in/evanphx/json-patch.v4 + version: v4.12.0 + retrieved: "2026-08-27" + commit: "" + sha256: eccb52c37cbf4a38d392101f2b31d424eca68bfebab3de4265d0cf595ca07b51 + license: BSD-3-Clause +- dependency: gopkg.in/inf.v0 + version: v0.9.1 + retrieved: "2026-08-27" + commit: "" + sha256: 08abac18c95cc43b725d4925f63309398d618beab68b4669659b61255e5374a0 + license: BSD-3-Clause +- dependency: gopkg.in/yaml.v2 + version: v2.4.0 + retrieved: "2026-08-27" + commit: "" + sha256: ede49e27c4cca6cdd2ec719aed8ea4d363710cceb3d411e7a786fbdec0d391fd + license: Apache-2.0 +- dependency: gopkg.in/yaml.v3 + version: v3.0.1 + retrieved: "2026-08-27" + commit: "" + sha256: aab8fbc4e6300ea08e6afe1caea18a21c90c79f489f52c53e2f20431f1a9a015 + license: + - Apache-2.0 + - MIT +- dependency: grpc + version: https://github.com/grpc/grpc/archive/refs/tags/v1.75.0.tar.gz + retrieved: "2026-08-27" + commit: 093085cc925e0d5aa6e92bc29e917f9bdc00add2 + sha256: 31d6c01c3bfa0a3584532665715e487703eb10d76bb30b4875e57d139c13bbf9 + license: + - Apache-2.0 + - BSD-3-Clause + - MPL-2.0 +- dependency: honnef.co/go/tools + version: v0.5.1 + retrieved: "2026-08-27" + commit: 56172d41b117cc2c2f99f65fe0a790c8d7d7ea66 + sha256: d728ff392fc5b6f676a30c36e9d0a5b85f6f2e06b4ebbb121c27d965cbdffa11 + license: MIT +- dependency: k8s.io/api + version: v0.32.3 + retrieved: "2026-08-27" + commit: 22c1e39ad8694905cfaad52a473d81f230d61bcb + sha256: 649689e30730c65a1ecfef5da339bfdebd9678f6c053c274cedec1923c3b1dcd + license: Apache-2.0 +- dependency: k8s.io/apimachinery + version: v0.32.3 + retrieved: "2026-08-27" + commit: 59e9003f02d6f0c8fff53719a7a0604ec82ee9a9 + sha256: 2deacae4f8299eb194c465935d016226dbf88c65e1d5e51778b6d3add0a6f7d9 + license: Apache-2.0 +- dependency: k8s.io/client-go + version: v0.32.3 + retrieved: "2026-08-27" + commit: c106b23895edc59ff05c65770a17e6a6d3caee66 + sha256: bb5c24908df4dc2b65918635a7cde6709545e5a8c4b36398622050acac880929 + license: Apache-2.0 +- dependency: k8s.io/klog/v2 + version: v2.130.1 + retrieved: "2026-08-27" + commit: 75663bb798999a49e3e4c0f2375ed5cca8164194 + sha256: 262b961175bbe8cf6ecf227d06303df2eb47bfb890e19d516846422f7d406cb9 + license: Apache-2.0 +- dependency: k8s.io/kube-openapi + version: v0.0.0-20241105132330-32ad38e42d3f + retrieved: "2026-08-27" + commit: 32ad38e42d3faf1ce94eb29f4ea6d763339b258e + sha256: f4db322a09b2c6f11a02a1eb592dd61d961816fea50914ecc7539892124fb547 + license: Apache-2.0 +- dependency: k8s.io/utils + version: v0.0.0-20241104100929-3ea5e8cea738 + retrieved: "2026-08-27" + commit: 3ea5e8cea73810f6d2951b37183cf91e19f63455 + sha256: 3d787d888485d08fda514dcc2c78461f0ad3dc70d489f0d84fb54e45fbf1deff + license: Apache-2.0 +- dependency: kythe_release + version: https://github.com/kythe/kythe/releases/download/v0.0.74/kythe-v0.0.74.tar.gz + retrieved: "2026-08-27" + commit: a182743b6a2315c3ffdd43ed6f008434948f2adc + sha256: 52a9c6e8111237043b8d0b6127637f0eb8d2ba3a776e4749b036e911ec3529ce + license: + - Apache-2.0 + - MIT +- dependency: llvm-raw + version: https://github.com/llvm/llvm-project/archive/cb2f0d0a5f14c183e7182aba0f0e54a518de9e3f.tar.gz + retrieved: "2026-08-27" + commit: cb2f0d0a5f14c183e7182aba0f0e54a518de9e3f + sha256: e8ece380fdb57dc6f8e42df9db872a1ade5056c5379075e3e2f99c89200aea69 + license: + - Apache-2.0 WITH LLVM-exception + - MIT +- dependency: nlohmann_json + version: https://github.com/nlohmann/json/releases/download/v3.11.3/include.zip + retrieved: "2026-08-27" + commit: 9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03 + sha256: a22461d13119ac5c78f205d3df1db13403e58ce1bb1794edc9313677313f4a9d + license: MIT +- dependency: platforms + version: https://github.com/bazelbuild/platforms/releases/download/1.0.0/platforms-1.0.0.tar.gz + retrieved: "2026-08-27" + commit: ab99943ab6bed53cff461a3afa99fc79d31e4351 + sha256: 3384eb1c30762704fbe38e440204e114154086c8fc8a8c2e3e28441028c019a8 + license: Apache-2.0 +- dependency: protobuf + version: https://github.com/protocolbuffers/protobuf/releases/download/v33.4/protobuf-33.4.bazel.tar.gz + retrieved: "2026-08-27" + commit: edaa823d8b36a8656d7b2b9241b7d0bfe50af878 + sha256: 687e98a471973b5c5fd711750c40b8b82c0ade33f649db65e00b290f29345a2b + license: BSD-3-Clause +- dependency: protoc-gen-validate + version: https://github.com/bufbuild/protoc-gen-validate/archive/refs/tags/v1.3.0.tar.gz + retrieved: "2026-08-27" + commit: 4eb9011f3e6d551d067d87c89f082261164fac31 + sha256: 26dc6db523ee943af51fdfc292658a2dbe7046735654f1dc839745988f0fe26b + license: Apache-2.0 +- dependency: rules_cc + version: https://github.com/bazelbuild/rules_cc/releases/download/0.2.16/rules_cc-0.2.16.tar.gz + retrieved: "2026-08-27" + commit: 6fd317b2ae0534a29db7085605b0262849e62f93 + sha256: 458b658277ba51b4730ea7a2020efdf1c6dcadf7d30de72e37f4308277fa8c01 + license: Apache-2.0 +- dependency: rules_go + version: https://github.com/bazel-contrib/rules_go/releases/download/v0.57.0/rules_go-v0.57.0.zip + retrieved: "2026-08-27" + commit: 64d9225db70d2e9014217fd8e0340ba4407066fb + sha256: a729c8ed2447c90fe140077689079ca0acfb7580ec41637f312d650ce9d93d96 + license: Apache-2.0 +- dependency: rules_license + version: https://github.com/bazelbuild/rules_license/releases/download/1.0.0/rules_license-1.0.0.tar.gz + retrieved: "2026-08-27" + commit: f85e7d6309f28f031bf049f7d6283ce0d41d7546 + sha256: 26d4021f6898e23b82ef953078389dd49ac2b5618ac564ade4ef87cced147b38 + license: Apache-2.0 +- dependency: rules_pkg + version: https://github.com/bazelbuild/rules_pkg/releases/download/1.0.1/rules_pkg-1.0.1.tar.gz + retrieved: "2026-08-27" + commit: 6a44f01087cf504eeee7dffce7cabe042a2f0bac + sha256: d20c951960ed77cb7b341c2a59488534e494d5ad1d30c4818c736d57772a9fef + license: Apache-2.0 +- dependency: rules_python + version: https://github.com/bazel-contrib/rules_python/releases/download/1.7.0/rules_python-1.7.0.tar.gz + retrieved: "2026-08-27" + commit: d3ea893113375b0c0f788c3315d8a8f488d69af6 + sha256: f609f341d6e9090b981b3f45324d05a819fd7a5a56434f849c761971ce2c47da + license: Apache-2.0 +- dependency: sigs.k8s.io/json + version: v0.0.0-20241010143419-9aa6b5e7a4b3 + retrieved: "2026-08-27" + commit: 9aa6b5e7a4b39ff56593bf75f44924cb195127af + sha256: 1b2efd5ae5bc3f0a01106096e23cbf17d36a9d860232cebfb4e3fdb182fa6dee + license: + - Apache-2.0 + - BSD-3-Clause +- dependency: sigs.k8s.io/structured-merge-diff/v4 + version: v4.4.2 + retrieved: "2026-08-27" + commit: ccf7a061f165cd3feee92af191157c053154d8bc + sha256: 18858d969f9d0ce09a5ce65e462154f7fcbce628dcefcc718cbbadac202c459d + license: Apache-2.0 +- dependency: sigs.k8s.io/yaml + version: v1.4.0 + retrieved: "2026-08-27" + commit: c3772b51db126345efe2dfe4ff8dac83b8141684 + sha256: ef031ff78ff9b7036e174eef49dfbd77468dc4f0afb73a639b61f8ab3a1cc425 + license: + - Apache-2.0 + - BSD-3-Clause + - MIT +- dependency: xds + version: https://github.com/cncf/xds/archive/ee656c7534f5d7dc23d44dd611689568f72017a6.tar.gz + retrieved: "2026-08-27" + commit: ee656c7534f5d7dc23d44dd611689568f72017a6 + sha256: 49535f3c3370004309da50194c09bbfc528d4702424dd46e7d56a278a3dfc15d + license: Apache-2.0 +- dependency: zstd + version: https://github.com/facebook/zstd/releases/download/v1.5.5/zstd-1.5.5.tar.gz + retrieved: "2026-08-27" + commit: 63779c798237346c2b245c546c40b72a5a5913fe + sha256: 9c4396cc829cfae319a6e2615202e82aad41372073482fce286fac78646d3ee4 + license: BSD-3-Clause diff --git a/tools/licensecheck/licensecheck.go b/tools/licensecheck/licensecheck.go new file mode 100644 index 00000000000..3fba45031ce --- /dev/null +++ b/tools/licensecheck/licensecheck.go @@ -0,0 +1,965 @@ +// Copyright 2026 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package licensecheck audits the licenses of gVisor's external dependencies. +// +// Dependencies are enumerated by asking Bazel (`make mod`, which wraps `bazel +// mod` in the dockerized build environment): the root module's direct +// bazel_deps, the http_archive/http_file repos declared in MODULE.bazel, and +// the Go repos imported from the go_deps extension, unioned with go.mod's +// requirements. Fetch downloads each dependency's license text, from the Go +// module proxy for Go modules and from GitHub for everything else, classifies +// it, and records it in a YAML file. Verify checks that the YAML file has an +// entry for every dependency, without fetching any licenses. +// +// Entries whose license cannot be fetched automatically (e.g. +// @google_root_pem) are maintained by hand: Fetch preserves an existing entry +// whenever fetching fails. +package licensecheck + +import ( + "archive/zip" + "bytes" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "net/http" + "os" + "os/exec" + "regexp" + "slices" + "sort" + "strings" + "sync" + "time" + + "golang.org/x/mod/modfile" + "golang.org/x/mod/module" + "golang.org/x/mod/semver" + yaml "gopkg.in/yaml.v3" +) + +// Paths locates the audit's inputs and output, relative to the repo root. +type Paths struct { + GoMod string + YAML string + Policy string +} + +// Entry records the license of a single dependency. +type Entry struct { + Dependency string `yaml:"dependency"` + Version string `yaml:"version"` + Retrieved string `yaml:"retrieved"` + Commit string `yaml:"commit"` + SHA256 string `yaml:"sha256"` + License Licenses `yaml:"license"` +} + +// License is an SPDX license identifier (https://spdx.org/licenses/), one of +// knownLicenses. +type License string + +const ( + apache2 License = "Apache-2.0" + apache2LLVM License = "Apache-2.0 WITH LLVM-exception" + bsd2 License = "BSD-2-Clause" + bsd3 License = "BSD-3-Clause" + bsd4 License = "BSD-4-Clause" + gpl2 License = "GPL-2.0-only" + gpl3 License = "GPL-3.0-only" + isc License = "ISC" + lgpl21 License = "LGPL-2.1-only" + lgpl3 License = "LGPL-3.0-only" + mit License = "MIT" + mpl2 License = "MPL-2.0" + unlicense License = "Unlicense" + // noAssertion is the SPDX token for dependencies to which no software + // license applies, e.g. a certificate bundle. + noAssertion License = "NOASSERTION" +) + +// knownLicenses is the set of known license identifiers. +var knownLicenses = map[License]bool{ + apache2: true, + apache2LLVM: true, + bsd2: true, + bsd3: true, + bsd4: true, + gpl2: true, + gpl3: true, + isc: true, + lgpl21: true, + lgpl3: true, + mit: true, + mpl2: true, + unlicense: true, + noAssertion: true, +} + +// Licenses is the sorted set of licenses that apply to a dependency. It +// marshals as a plain string when there is a single license and as a list +// otherwise. +type Licenses []License + +// String implements fmt.Stringer.String. +func (l Licenses) String() string { + s := make([]string, len(l)) + for i, license := range l { + s[i] = string(license) + } + return strings.Join(s, ", ") +} + +// MarshalYAML implements yaml.Marshaler.MarshalYAML. +func (l Licenses) MarshalYAML() (any, error) { + if len(l) == 1 { + return string(l[0]), nil + } + s := make([]string, len(l)) + for i, license := range l { + s[i] = string(license) + } + return s, nil +} + +// UnmarshalYAML implements yaml.Unmarshaler.UnmarshalYAML. +func (l *Licenses) UnmarshalYAML(node *yaml.Node) error { + switch node.Kind { + case yaml.ScalarNode: + var s string + if err := node.Decode(&s); err != nil { + return err + } + *l = Licenses{License(s)} + return nil + case yaml.SequenceNode: + var s []string + if err := node.Decode(&s); err != nil { + return err + } + if len(s) < 2 { + return errors.New("a license list must have at least two entries; use a plain string for a single license") + } + *l = make(Licenses, len(s)) + for i, license := range s { + (*l)[i] = License(license) + } + return nil + default: + return errors.New("license must be a string or a list of strings") + } +} + +type depKind int + +const ( + kindGoModule depKind = iota + kindArchive +) + +// dep is a single external dependency. +type dep struct { + name string + kind depKind + version string // kindGoModule. + url string // kindArchive. + sha256 string // kindArchive: the hash Bazel pins for the archive. +} + +// source identifies the audited version of a dependency: the Go module +// version, or the source archive URL. +func (d dep) source() string { + if d.kind == kindGoModule { + return d.version + } + return d.url +} + +const ( + yamlHeader = `# Licenses of gVisor's external dependencies, as declared in MODULE.bazel and +# go.mod. +# Regenerate with: make run TARGETS=//tools/licensecheck/main:licensecheck ARGS=--mode=fetch +# Check completeness with ARGS=--mode=verify. +# Entries whose license cannot be fetched automatically are maintained by hand and preserved +# by --mode=fetch, so you can hand-edit the file for these. +` + dateFormat = "2006-01-02" + fetchWorkers = 8 +) + +// Fetch retrieves the license of every dependency and rewrites the YAML file. +func Fetch(p Paths) error { + deps, err := enumerate(p) + if err != nil { + return err + } + old, err := readEntries(p.YAML) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return err + } + oldByName := make(map[string]Entry) + for _, e := range old { + oldByName[e.Dependency] = e + } + + type result struct { + fetched *fetched + err error + } + results := make([]result, len(deps)) + sem := make(chan struct{}, fetchWorkers) + var wg sync.WaitGroup + for i, d := range deps { + wg.Add(1) + go func(i int, d dep) { + defer wg.Done() + sem <- struct{}{} + defer func() { <-sem }() + f, err := fetchLicense(d) + results[i] = result{f, err} + }(i, d) + } + wg.Wait() + + today := time.Now().UTC().Format(dateFormat) + var entries []Entry + var failed []string + for i, d := range deps { + r := results[i] + if r.err != nil { + if e, ok := oldByName[d.name]; ok && len(e.License) > 0 { + fmt.Fprintf(os.Stderr, "%-60s %s (kept existing entry: %v)\n", d.name, e.License, r.err) + entries = append(entries, e) + continue + } + fmt.Fprintf(os.Stderr, "%-60s FAILED: %v\n", d.name, r.err) + failed = append(failed, d.name) + continue + } + e := Entry{ + Dependency: d.name, + Version: d.source(), + Retrieved: today, + Commit: r.fetched.commit, + SHA256: r.fetched.sha256, + License: r.fetched.license, + } + // Keep the old retrieval date when nothing changed, so that re-running + // fetch does not churn the file. + if o, ok := oldByName[d.name]; ok && o.Version == e.Version && o.Commit == e.Commit && + o.SHA256 == e.SHA256 && slices.Equal(o.License, e.License) { + e.Retrieved = o.Retrieved + } + fmt.Fprintf(os.Stderr, "%-60s %s\n", d.name, e.License) + entries = append(entries, e) + } + if err := writeEntries(p.YAML, entries); err != nil { + return err + } + if len(failed) > 0 { + return fmt.Errorf("cannot fetch licenses for %s; add entries to %s by hand", strings.Join(failed, ", "), p.YAML) + } + return nil +} + +// Verify checks that the YAML file has a well-formed, up-to-date entry for +// every dependency and no entries for dependencies that no longer exist. +func Verify(p Paths) error { + deps, err := enumerate(p) + if err != nil { + return err + } + entries, err := readEntries(p.YAML) + if err != nil { + return err + } + policy, err := ReadPolicy(p.Policy) + if err != nil { + return err + } + problems := append(verifyProblems(deps, entries), CheckPolicy(entries, policy)...) + if len(problems) > 0 { + sort.Strings(problems) + for _, problem := range problems { + fmt.Fprintln(os.Stderr, problem) + } + return fmt.Errorf("%d problems; regenerate %s with licensecheck --mode=fetch, or amend %s", len(problems), p.YAML, p.Policy) + } + fmt.Printf("all %d dependencies have up-to-date license entries that conform to %s\n", len(deps), p.Policy) + return nil +} + +// verifyProblems returns one problem per missing, malformed, out-of-date, or +// stale entry. +func verifyProblems(deps []dep, entries []Entry) []string { + byName := make(map[string]Entry) + var problems []string + for _, e := range entries { + if _, ok := byName[e.Dependency]; ok { + problems = append(problems, fmt.Sprintf("duplicate entry for %s", e.Dependency)) + } + byName[e.Dependency] = e + } + depSet := make(map[string]bool) + for _, d := range deps { + depSet[d.name] = true + e, ok := byName[d.name] + switch { + case !ok: + problems = append(problems, fmt.Sprintf("missing entry for %s", d.name)) + case len(e.License) == 0: + problems = append(problems, fmt.Sprintf("%s has no license", d.name)) + default: + if e.Version != d.source() { + problems = append(problems, fmt.Sprintf("%s was audited at %q, but is now %q", d.name, e.Version, d.source())) + } + // Archive hashes are pinned by Bazel, so verify can cross-check + // them offline. Go module hashes are only computed by fetch. + if d.sha256 != "" && e.SHA256 != d.sha256 { + problems = append(problems, fmt.Sprintf("%s was audited with sha256 %q, but is now pinned to %q", d.name, e.SHA256, d.sha256)) + } + for i, license := range e.License { + if !knownLicenses[license] { + problems = append(problems, fmt.Sprintf("%s has unknown license %q", d.name, license)) + } + if i > 0 && e.License[i-1] >= license { + problems = append(problems, fmt.Sprintf("licenses of %s are not sorted and unique", d.name)) + break + } + } + if _, err := time.Parse(dateFormat, e.Retrieved); err != nil { + problems = append(problems, fmt.Sprintf("%s has invalid retrieval date %q", d.name, e.Retrieved)) + } + } + } + for name := range byName { + if !depSet[name] { + problems = append(problems, fmt.Sprintf("stale entry for %s, which is no longer a dependency", name)) + } + } + return problems +} + +// enumerate returns all external dependencies, sorted by name. +func enumerate(p Paths) ([]dep, error) { + graphOut, err := makeMod("graph", "--depth", "1", "--extension_info=all", "--output", "json") + if err != nil { + return nil, err + } + graph, err := parseModGraph(graphOut) + if err != nil { + return nil, err + } + // refs are the repos to audit: modules as name@version, everything else + // by apparent repo name. + type repoRef struct { + ref, name string + } + var refs []repoRef + for _, m := range graph.Dependencies { + refs = append(refs, repoRef{m.Key, m.Name}) + } + for _, u := range graph.ExtensionUsages { + switch { + case strings.HasPrefix(u.Key, "@@//:MODULE.bazel%") && + (strings.HasSuffix(u.Key, " http_archive") || strings.HasSuffix(u.Key, " http_file")): + // http_archive/http_file declared directly in MODULE.bazel. + case strings.HasSuffix(u.Key, "%go_deps"): + // Go repos imported from the gazelle go_deps extension. + default: + // Toolchain extensions (crosstool, llvm_zlib, python, go_sdk, + // ...) are not audited. + continue + } + for _, r := range u.UsedRepos { + refs = append(refs, repoRef{"@" + r, r}) + } + } + refNames := make([]string, 0, len(refs)+1) + refNames = append(refNames, "show_repo") + for _, r := range refs { + refNames = append(refNames, r.ref) + } + showOut, err := makeMod(refNames...) + if err != nil { + return nil, err + } + repos, err := parseShowRepos(showOut) + if err != nil { + return nil, err + } + + goModData, err := os.ReadFile(p.GoMod) + if err != nil { + return nil, err + } + goMod, err := modfile.Parse(p.GoMod, goModData, nil) + if err != nil { + return nil, err + } + goVersions := make(map[string]string) + for _, r := range goMod.Require { + goVersions[r.Mod.Path] = r.Mod.Version + } + + var deps []dep + for _, r := range refs { + repo, ok := repos[r.ref] + if !ok { + return nil, fmt.Errorf("bazel mod show_repo did not report %s", r.ref) + } + switch repo.rule { + case "go_repository": + path, version := repo.first("importpath"), repo.first("version") + if path == "" || version == "" { + return nil, fmt.Errorf("go_repository %s has no importpath or version", r.name) + } + // Bazel resolves Go modules across go.mod and go_deps.module + // declarations; for modules in both, pick the higher version. + if v, ok := goVersions[path]; !ok || semver.Compare(version, v) > 0 { + goVersions[path] = version + } + case "http_archive", "http_file": + urls := append(repo.attrs["url"], repo.attrs["urls"]...) + if len(urls) == 0 { + return nil, fmt.Errorf("%s %s has no URL", repo.rule, r.name) + } + deps = append(deps, dep{name: r.name, kind: kindArchive, url: pickURL(urls), sha256: repoSHA256(repo)}) + case "local_repository", "new_local_repository": + // Local paths are part of the gVisor checkout, not external + // dependencies. + default: + return nil, fmt.Errorf("unsupported repository rule %s for %s", repo.rule, r.name) + } + } + for path, version := range goVersions { + deps = append(deps, dep{name: path, kind: kindGoModule, version: version}) + } + sort.Slice(deps, func(i, j int) bool { return deps[i].name < deps[j].name }) + for i := 1; i < len(deps); i++ { + if deps[i].name == deps[i-1].name { + return nil, fmt.Errorf("duplicate dependency name %q", deps[i].name) + } + } + return deps, nil +} + +// makeMod runs `make mod TARGETS="..."`, which wraps `bazel mod`. +func makeMod(args ...string) (string, error) { + cmd := exec.Command("make", "-s", "mod", "OPTIONS=", "TARGETS="+strings.Join(args, " ")) + var stdout, stderr bytes.Buffer + cmd.Stdout = &stdout + cmd.Stderr = &stderr + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("cannot run make mod %s: %w\n%s", strings.Join(args, " "), err, stderr.String()) + } + return stdout.String(), nil +} + +// modGraph is the subset of `bazel mod graph --output json` that enumerate +// uses. +type modGraph struct { + Dependencies []struct { + Key string `json:"key"` // "name@version". + Name string `json:"name"` // Module name. + } `json:"dependencies"` + ExtensionUsages []struct { + Key string `json:"key"` + UsedRepos []string `json:"used_repos"` + } `json:"extensionUsages"` +} + +// parseModGraph parses `bazel mod graph --output json` output, skipping any +// container-management noise preceding the JSON document. +func parseModGraph(out string) (*modGraph, error) { + start := strings.Index(out, "{") + if start < 0 { + return nil, fmt.Errorf("no JSON in bazel mod graph output %q", out) + } + var graph modGraph + if err := json.Unmarshal([]byte(out[start:]), &graph); err != nil { + return nil, fmt.Errorf("cannot parse bazel mod graph output: %w", err) + } + return &graph, nil +} + +// repoInfo is one repo definition reported by `bazel mod show_repo`: the +// repository rule name and, for each attribute, the string literals in its +// value. +type repoInfo struct { + rule string + attrs map[string][]string +} + +func (r repoInfo) first(attr string) string { + if v := r.attrs[attr]; len(v) > 0 { + return v[0] + } + return "" +} + +var ( + repoHeaderRE = regexp.MustCompile(`(?m)^## (.+):$`) + repoRuleRE = regexp.MustCompile(`^([A-Za-z_]\w*)\($`) + repoAttrRE = regexp.MustCompile(`^ (\w+) = (.*),$`) + quotedRE = regexp.MustCompile(`"((?:[^"\\]|\\.)*)"`) +) + +// parseShowRepos parses `bazel mod show_repo` output into one repoInfo per +// "## :" section. +func parseShowRepos(out string) (map[string]repoInfo, error) { + repos := make(map[string]repoInfo) + headers := repoHeaderRE.FindAllStringSubmatchIndex(out, -1) + for i, h := range headers { + ref := out[h[2]:h[3]] + end := len(out) + if i+1 < len(headers) { + end = headers[i+1][0] + } + info := repoInfo{attrs: make(map[string][]string)} + for _, line := range strings.Split(out[h[1]:end], "\n") { + if strings.HasPrefix(line, "#") { + continue + } + if m := repoRuleRE.FindStringSubmatch(line); m != nil && info.rule == "" { + info.rule = m[1] + continue + } + if m := repoAttrRE.FindStringSubmatch(line); m != nil { + var values []string + for _, q := range quotedRE.FindAllStringSubmatch(m[2], -1) { + values = append(values, q[1]) + } + info.attrs[m[1]] = values + } + } + if info.rule == "" { + return nil, fmt.Errorf("no repository rule in show_repo output for %s", ref) + } + repos[ref] = info + } + if len(repos) == 0 { + return nil, fmt.Errorf("no repos in bazel mod show_repo output %q", out) + } + return repos, nil +} + +// pickURL returns the first URL whose license source is known, defaulting to +// the first URL. +func pickURL(urls []string) string { + for _, u := range urls { + if _, _, _, err := parseGitHubURL(u); err == nil { + return u + } + } + return urls[0] +} + +// repoSHA256 returns the hex sha256 that Bazel pins for an archive repo, +// from its sha256 or "sha256-"-prefixed integrity attribute. +// Empty string if the repo is unpinned (e.g. @google_root_pem). +func repoSHA256(r repoInfo) string { + if s := r.first("sha256"); s != "" { + return s + } + if integrity := r.first("integrity"); strings.HasPrefix(integrity, "sha256-") { + if b, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(integrity, "sha256-")); err == nil { + return hex.EncodeToString(b) + } + } + return "" +} + +const goProxy = "https://proxy.golang.org" + +var ( + errNotFound = errors.New("not found") + httpClient = &http.Client{Timeout: 2 * time.Minute} +) + +// httpGet fetches a URL, retrying transient failures once. 404 and 410 are +// permanent and reported as errNotFound. +func httpGet(url string, header map[string]string) ([]byte, error) { + var lastErr error + for attempt := 0; attempt < 2; attempt++ { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + req.Header.Set("User-Agent", "gvisor.dev licensecheck") + for k, v := range header { + req.Header.Set(k, v) + } + resp, err := httpClient.Do(req) + if err != nil { + lastErr = err + continue + } + body, err := io.ReadAll(resp.Body) + resp.Body.Close() + switch { + case err != nil: + lastErr = err + case resp.StatusCode == http.StatusNotFound || resp.StatusCode == http.StatusGone: + return nil, fmt.Errorf("%s: %w", url, errNotFound) + case resp.StatusCode != http.StatusOK: + lastErr = fmt.Errorf("GET %s: %s", url, resp.Status) + default: + return body, nil + } + } + return nil, lastErr +} + +// fetched is the result of fetching one dependency's license. +type fetched struct { + commit string + sha256 string + license Licenses +} + +// fetchLicense returns the upstream commit, artifact hash, and licenses of a +// dependency. +func fetchLicense(d dep) (*fetched, error) { + if d.kind == kindGoModule { + return fetchGoModule(d.name, d.version) + } + f, err := fetchGitHub(d.url) + if err != nil { + return nil, err + } + f.sha256 = d.sha256 + return f, nil +} + +// licenseFileNames are candidate top-level license files, in priority order. +var licenseFileNames = []string{ + "LICENSE", "LICENSE.txt", "LICENSE.md", "LICENSE.TXT", "LICENSE.MIT", + "LICENSE-APACHE-2.0.txt", "LICENCE", "COPYING", "License", "license.md", +} + +var pseudoVersionRE = regexp.MustCompile(`\d{14}-([0-9a-f]{12})$`) + +// fetchGoModule downloads a module from the Go module proxy and classifies +// the license file at its root. +func fetchGoModule(path, version string) (*fetched, error) { + escPath, err := module.EscapePath(path) + if err != nil { + return nil, err + } + escVersion, err := module.EscapeVersion(version) + if err != nil { + return nil, err + } + base := fmt.Sprintf("%s/%s/@v/%s", goProxy, escPath, escVersion) + var commit string + if body, err := httpGet(base+".info", nil); err == nil { + var info struct { + Origin struct{ Hash string } + } + if json.Unmarshal(body, &info) == nil { + commit = info.Origin.Hash + } + } + if commit == "" { + if m := pseudoVersionRE.FindStringSubmatch(version); m != nil { + commit = m[1] + } + } + // Older modules have no Origin metadata in the proxy; for GitHub-hosted + // ones, fall back to resolving the version tag. Best-effort: the commit + // stays empty if this fails. + if commit == "" && strings.HasPrefix(path, "github.com/") { + prefix, _, _ := module.SplitPathVersion(path) + if parts := strings.SplitN(prefix, "/", 4); len(parts) >= 3 { + tag, _, _ := strings.Cut(version, "+") + if len(parts) == 4 { + // Tags of modules in a subdirectory are prefixed with it. + tag = parts[3] + "/" + tag + } + if c, err := resolveGitHubCommit(parts[1], parts[2], tag); err == nil { + commit = c + } + } + } + zipBody, err := httpGet(base+".zip", nil) + if err != nil { + return nil, fmt.Errorf("cannot download module zip: %w", err) + } + zipSum := sha256.Sum256(zipBody) + zipReader, err := zip.NewReader(bytes.NewReader(zipBody), int64(len(zipBody))) + if err != nil { + return nil, fmt.Errorf("cannot read module zip: %w", err) + } + rootFiles := make(map[string]*zip.File) + prefix := path + "@" + version + "/" + for _, f := range zipReader.File { + if rest, ok := strings.CutPrefix(f.Name, prefix); ok && !strings.Contains(rest, "/") { + rootFiles[rest] = f + } + } + for _, name := range licenseFileNames { + f, ok := rootFiles[name] + if !ok { + continue + } + r, err := f.Open() + if err != nil { + return nil, err + } + text, err := io.ReadAll(r) + r.Close() + if err != nil { + return nil, err + } + license, err := classify(string(text)) + if err != nil { + return nil, fmt.Errorf("%s: %w", name, err) + } + return &fetched{commit: commit, sha256: hex.EncodeToString(zipSum[:]), license: license}, nil + } + return nil, errors.New("no license file at module root") +} + +var githubURLRegexps = []*regexp.Regexp{ + regexp.MustCompile(`^https://github\.com/([^/]+)/([^/]+)/releases/download/([^/]+)/`), + regexp.MustCompile(`^https://github\.com/([^/]+)/([^/]+)/archive/refs/tags/(.+?)\.(?:tar\.gz|tar\.xz|tar\.bz2|zip)$`), + regexp.MustCompile(`^https://github\.com/([^/]+)/([^/]+)/archive/(.+?)\.(?:tar\.gz|tar\.xz|tar\.bz2|zip)$`), + regexp.MustCompile(`^https://raw\.githubusercontent\.com/([^/]+)/([^/]+)/([^/]+)/`), +} + +// parseGitHubURL extracts (owner, repo, ref) from the archive and raw-file +// URL used by MODULE.bazel and the Bazel Central Registry. +func parseGitHubURL(url string) (owner, repo, ref string, err error) { + for _, re := range githubURLRegexps { + if m := re.FindStringSubmatch(url); m != nil { + return m[1], m[2], m[3], nil + } + } + return "", "", "", fmt.Errorf("cannot determine license source for %q", url) +} + +// fetchGitHub resolves a source URL to a GitHub repo and classifies the +// license file at its root. +func fetchGitHub(url string) (*fetched, error) { + owner, repo, ref, err := parseGitHubURL(url) + if err != nil { + return nil, err + } + commit, err := resolveGitHubCommit(owner, repo, ref) + if err != nil { + return nil, fmt.Errorf("cannot resolve commit for %s/%s@%s: %w", owner, repo, ref, err) + } + for _, name := range licenseFileNames { + body, err := httpGet(fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/%s", owner, repo, ref, name), nil) + if errors.Is(err, errNotFound) { + continue + } + if err != nil { + return nil, err + } + license, err := classify(string(body)) + if err != nil { + return nil, fmt.Errorf("%s: %w", name, err) + } + return &fetched{commit: commit, license: license}, nil + } + return nil, fmt.Errorf("no license file in %s/%s@%s", owner, repo, ref) +} + +var commitRE = regexp.MustCompile(`^[0-9a-f]{40}$`) + +// resolveGitHubCommit resolves a tag or abbreviated hash to a full commit +// hash. GITHUB_TOKEN, if set, raises the API rate limit. +func resolveGitHubCommit(owner, repo, ref string) (string, error) { + if commitRE.MatchString(ref) { + return ref, nil + } + header := map[string]string{"Accept": "application/vnd.github.sha"} + if token := os.Getenv("GITHUB_TOKEN"); token != "" { + header["Authorization"] = "Bearer " + token + } + body, err := httpGet(fmt.Sprintf("https://api.github.com/repos/%s/%s/commits/%s", owner, repo, ref), header) + if err != nil { + return "", err + } + commit := strings.TrimSpace(string(body)) + if !commitRE.MatchString(commit) { + return "", fmt.Errorf("unexpected GitHub API response %q", commit) + } + return commit, nil +} + +// classify maps license text to the set of licenses it contains. +// Detection looks for phrases unique to each license and reports all that +// match. The GNU patterns match the dated titles of the full license texts, +// so that passing references (e.g. in MPL-2.0's "Secondary License" clause) +// do not trigger them. +func classify(text string) (Licenses, error) { + t := strings.ToLower(strings.Join(strings.Fields(text), " ")) + var ids Licenses + if strings.Contains(t, "apache license") && strings.Contains(t, "version 2.0") { + if strings.Contains(t, "llvm exceptions") { + ids = append(ids, apache2LLVM) + } else { + ids = append(ids, apache2) + } + } + if strings.Contains(t, "permission is hereby granted, free of charge") { + ids = append(ids, mit) + } + if strings.Contains(t, "redistribution and use in source and binary forms") { + switch { + case strings.Contains(t, "all advertising materials"): + ids = append(ids, bsd4) + case strings.Contains(t, "neither the name"): + ids = append(ids, bsd3) + default: + ids = append(ids, bsd2) + } + } + if strings.Contains(t, "mozilla public license version 2.0") { + ids = append(ids, mpl2) + } + if strings.Contains(t, "gnu lesser general public license version 2.1, february 1999") { + ids = append(ids, lgpl21) + } + if strings.Contains(t, "gnu lesser general public license version 3, 29 june 2007") { + ids = append(ids, lgpl3) + } + if strings.Contains(t, "gnu general public license version 2, june 1991") { + ids = append(ids, gpl2) + } + if strings.Contains(t, "gnu general public license version 3, 29 june 2007") { + ids = append(ids, gpl3) + } + if strings.Contains(t, "permission to use, copy, modify") && strings.Contains(t, "distribute this software for any purpose") { + ids = append(ids, isc) + } + if strings.Contains(t, "this is free and unencumbered software") { + ids = append(ids, unlicense) + } + if len(ids) == 0 { + return nil, errors.New("cannot classify license text") + } + slices.Sort(ids) + return ids, nil +} + +// Policy is a dependency licensing policy (governance/licensing.yaml). +type Policy struct { + AllowedLicenses []License `yaml:"allowed_licenses"` + Exceptions []Exception `yaml:"exceptions"` +} + +// Exception allows one dependency to use licenses outside +// Policy.AllowedLicenses. License must match the dependency's entry exactly, +// so that license changes appear during review. +type Exception struct { + Dependency string `yaml:"dependency"` + License Licenses `yaml:"license"` + ExceptionRationale string `yaml:"exception_rationale"` +} + +// ReadPolicy loads a licensing policy file. +func ReadPolicy(path string) (*Policy, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var policy Policy + if err := yaml.Unmarshal(data, &policy); err != nil { + return nil, fmt.Errorf("%s: %w", path, err) + } + return &policy, nil +} + +// CheckPolicy returns a problem for every dependency whose licenses are +// neither all in policy.AllowedLicenses nor covered by an exception, and for +// every malformed, stale, or unnecessary exception. +func CheckPolicy(entries []Entry, policy *Policy) []string { + allowed := make(map[License]bool) + for _, license := range policy.AllowedLicenses { + allowed[license] = true + } + conforms := func(l Licenses) bool { + for _, license := range l { + if !allowed[license] { + return false + } + } + return len(l) > 0 + } + byName := make(map[string]Entry) + for _, e := range entries { + byName[e.Dependency] = e + } + var problems []string + exceptions := make(map[string]bool) + for i, x := range policy.Exceptions { + if exceptions[x.Dependency] { + problems = append(problems, fmt.Sprintf("duplicate exception for %s", x.Dependency)) + } + exceptions[x.Dependency] = true + if i > 0 && policy.Exceptions[i-1].Dependency >= x.Dependency { + problems = append(problems, fmt.Sprintf("exceptions are not sorted by dependency at %s", x.Dependency)) + } + if x.ExceptionRationale == "" { + problems = append(problems, fmt.Sprintf("exception for %s has no rationale", x.Dependency)) + } + e, ok := byName[x.Dependency] + switch { + case !ok: + problems = append(problems, fmt.Sprintf("exception for %s, which is not a dependency", x.Dependency)) + case !slices.Equal(x.License, e.License): + problems = append(problems, fmt.Sprintf("exception for %s lists licenses %v, but the dependency uses %v", x.Dependency, x.License, e.License)) + case conforms(e.License): + problems = append(problems, fmt.Sprintf("unnecessary exception for %s, whose licenses are all allowed", x.Dependency)) + } + } + for _, e := range entries { + if !conforms(e.License) && !exceptions[e.Dependency] { + problems = append(problems, fmt.Sprintf("%s uses disallowed licenses %v and has no exception in the policy", e.Dependency, e.License)) + } + } + return problems +} + +// readEntries loads the YAML file. +func readEntries(path string) ([]Entry, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + var entries []Entry + if err := yaml.Unmarshal(data, &entries); err != nil { + return nil, fmt.Errorf("%s: %w", path, err) + } + return entries, nil +} + +// writeEntries writes the YAML file. +func writeEntries(path string, entries []Entry) error { + data, err := yaml.Marshal(entries) + if err != nil { + return err + } + return os.WriteFile(path, append([]byte(yamlHeader), data...), 0644) +} diff --git a/tools/licensecheck/licensecheck_test.go b/tools/licensecheck/licensecheck_test.go new file mode 100644 index 00000000000..944521e6640 --- /dev/null +++ b/tools/licensecheck/licensecheck_test.go @@ -0,0 +1,215 @@ +// Copyright 2026 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package licensecheck + +import ( + "os" + "path/filepath" + "slices" + "strings" + "testing" +) + +func TestClassify(t *testing.T) { + for _, test := range []struct { + name, text string + want Licenses + wantErr bool + }{ + { + name: "apache", + text: "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/", + want: Licenses{apache2}, + }, + { + name: "apache-llvm", + text: "Apache License v2.0 with LLVM Exceptions\n\nApache License\nVersion 2.0, January 2004", + want: Licenses{apache2LLVM}, + }, + { + name: "mit", + text: "Permission is hereby granted, free of charge, to any person obtaining a copy of this software", + want: Licenses{mit}, + }, + { + name: "bsd3", + text: "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n3. Neither the name of the copyright holder...", + want: Licenses{bsd3}, + }, + { + name: "bsd2", + text: "Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1... 2...", + want: Licenses{bsd2}, + }, + { + name: "isc", + text: "Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted", + want: Licenses{isc}, + }, + { + name: "lgpl21", + text: "GNU LESSER GENERAL PUBLIC LICENSE\nVersion 2.1, February 1999", + want: Licenses{lgpl21}, + }, + { + name: "dual sorted", + text: "Permission is hereby granted, free of charge, ...\n...\nApache License\nVersion 2.0", + want: Licenses{apache2, mit}, + }, + { + // MPL-2.0 mentions the GNU licenses in its "Secondary License" + // clause; that must not count as LGPL/GPL. + name: "mpl mentioning gnu", + text: "Mozilla Public License Version 2.0\n1.12. \"Secondary License\" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses.", + want: Licenses{mpl2}, + }, + { + name: "unknown", + text: "All rights reserved. Do not redistribute.", + wantErr: true, + }, + } { + got, err := classify(test.text) + if (err != nil) != test.wantErr { + t.Errorf("%s: err = %v, wantErr = %t", test.name, err, test.wantErr) + continue + } + if !slices.Equal(got, test.want) { + t.Errorf("%s: classify = %v, want %v", test.name, got, test.want) + } + for _, license := range got { + if !knownLicenses[license] { + t.Errorf("%s: classify returned unknown license %q", test.name, license) + } + } + } +} + +func TestVerifyProblems(t *testing.T) { + deps := []dep{ + {name: "example.com/mod", kind: kindGoModule, version: "v1.2.0"}, + {name: "some-archive", kind: kindArchive, url: "https://github.com/a/b/archive/refs/tags/v3.tar.gz", sha256: "cafe"}, + } + entries := []Entry{ + {Dependency: "example.com/mod", Version: "v1.2.0", Retrieved: "2026-08-26", Commit: "abc", SHA256: "beef", License: Licenses{mit}}, + {Dependency: "some-archive", Version: "https://github.com/a/b/archive/refs/tags/v3.tar.gz", Retrieved: "2026-08-26", Commit: "def", SHA256: "cafe", License: Licenses{apache2}}, + } + if problems := verifyProblems(deps, entries); len(problems) != 0 { + t.Errorf("verifyProblems on up-to-date entries = %v, want none", problems) + } + // A version bump without re-fetching must be flagged, for both kinds. + deps[0].version = "v1.3.0" + deps[1].url = "https://github.com/a/b/archive/refs/tags/v4.tar.gz" + problems := verifyProblems(deps, entries) + if len(problems) != 2 || + !strings.Contains(problems[0], `example.com/mod was audited at "v1.2.0", but is now "v1.3.0"`) || + !strings.Contains(problems[1], "some-archive was audited at") { + t.Errorf("verifyProblems after version bump = %v, want two audited-at problems", problems) + } + // A changed archive pin (same URL) must also be flagged. + deps[0].version = "v1.2.0" + deps[1].url = "https://github.com/a/b/archive/refs/tags/v3.tar.gz" + deps[1].sha256 = "d00d" + problems = verifyProblems(deps, entries) + if len(problems) != 1 || !strings.Contains(problems[0], `some-archive was audited with sha256 "cafe", but is now pinned to "d00d"`) { + t.Errorf("verifyProblems after pin change = %v, want one sha256 problem", problems) + } + // Missing and stale entries are still flagged. + deps[0].version = "v1.2.0" + deps[1].name = "renamed-archive" + problems = verifyProblems(deps, entries) + if len(problems) != 2 || + !strings.Contains(problems[0], "missing entry for renamed-archive") || + !strings.Contains(problems[1], "stale entry for some-archive") { + t.Errorf("verifyProblems after rename = %v, want missing+stale", problems) + } +} + +// repoRoot finds the directory containing both YAML files: the checkout root +// under `go test`, the runfiles root under Bazel. +func repoRoot(t *testing.T) string { + t.Helper() + dir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + for range 8 { + if _, err := os.Stat(filepath.Join(dir, "governance/licensing.yaml")); err == nil { + return dir + } + dir = filepath.Dir(dir) + } + t.Fatal("cannot find governance/licensing.yaml above the working directory") + return "" +} + +// TestLicensePolicy verifies that every dependency in dependencies.yaml +// either uses only licenses in governance/licensing.yaml's allowed_licenses, +// or is on its exceptions list. +func TestLicensePolicy(t *testing.T) { + root := repoRoot(t) + entries, err := readEntries(filepath.Join(root, "tools/licensecheck/dependencies.yaml")) + if err != nil { + t.Fatalf("cannot read dependencies.yaml: %v", err) + } + policy, err := ReadPolicy(filepath.Join(root, "governance/licensing.yaml")) + if err != nil { + t.Fatalf("cannot read licensing.yaml: %v", err) + } + for _, problem := range CheckPolicy(entries, policy) { + t.Error(problem) + } +} + +func TestCheckPolicy(t *testing.T) { + entries := []Entry{ + {Dependency: "a", License: Licenses{mit}}, + {Dependency: "b", License: Licenses{mpl2}}, + {Dependency: "c", License: Licenses{apache2, mpl2}}, + {Dependency: "d", License: Licenses{gpl2}}, + } + policy := &Policy{ + AllowedLicenses: []License{apache2, mit}, + Exceptions: []Exception{ + {Dependency: "b", License: Licenses{mpl2}, ExceptionRationale: "vendored"}, + {Dependency: "c", License: Licenses{apache2, mpl2}, ExceptionRationale: "notices"}, + }, + } + if problems := CheckPolicy(entries, policy); len(problems) != 1 || !strings.Contains(problems[0], "d uses disallowed licenses") { + t.Errorf("CheckPolicy = %v, want a single problem for d", problems) + } + // A license mismatch, a stale exception, an unnecessary exception, and + // the now-uncovered c and d are all flagged. + policy.Exceptions = []Exception{ + {Dependency: "a", License: Licenses{mit}, ExceptionRationale: "redundant"}, + {Dependency: "b", License: Licenses{unlicense}, ExceptionRationale: "wrong license"}, + {Dependency: "z", License: Licenses{mpl2}, ExceptionRationale: "no longer a dependency"}, + } + problems := CheckPolicy(entries, policy) + for _, want := range []string{ + "unnecessary exception for a", + "exception for b lists licenses Unlicense", + "exception for z, which is not a dependency", + "c uses disallowed licenses", + "d uses disallowed licenses", + } { + if !slices.ContainsFunc(problems, func(p string) bool { return strings.Contains(p, want) }) { + t.Errorf("CheckPolicy = %v, missing problem %q", problems, want) + } + } + if len(problems) != 5 { + t.Errorf("CheckPolicy returned %d problems, want 5: %v", len(problems), problems) + } +} diff --git a/tools/licensecheck/main/BUILD b/tools/licensecheck/main/BUILD new file mode 100644 index 00000000000..f541437ae28 --- /dev/null +++ b/tools/licensecheck/main/BUILD @@ -0,0 +1,12 @@ +load("//tools:defs.bzl", "go_binary") + +package( + default_applicable_licenses = ["//:license"], + licenses = ["notice"], +) + +go_binary( + name = "licensecheck", + srcs = ["main.go"], + deps = ["//tools/licensecheck"], +) diff --git a/tools/licensecheck/main/main.go b/tools/licensecheck/main/main.go new file mode 100644 index 00000000000..9f494892e7c --- /dev/null +++ b/tools/licensecheck/main/main.go @@ -0,0 +1,60 @@ +// Copyright 2026 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Binary licensecheck audits the licenses of gVisor's external dependencies. +package main + +import ( + "flag" + "fmt" + "os" + + "gvisor.dev/gvisor/tools/licensecheck" +) + +var ( + mode = flag.String("mode", "", "one of: fetch, verify") + goMod = flag.String("go_mod", "go.mod", "path to go.mod") + yamlPath = flag.String("yaml", "tools/licensecheck/dependencies.yaml", "path to the license YAML file") + policy = flag.String("policy", "governance/licensing.yaml", "path to the licensing policy YAML file") +) + +func main() { + flag.Parse() + if wd := os.Getenv("BUILD_WORKSPACE_DIRECTORY"); wd != "" { + if err := os.Chdir(wd); err != nil { + fmt.Fprintf(os.Stderr, "licensecheck: %v\n", err) + os.Exit(1) + } + } + p := licensecheck.Paths{ + GoMod: *goMod, + YAML: *yamlPath, + Policy: *policy, + } + var err error + switch *mode { + case "fetch": + err = licensecheck.Fetch(p) + case "verify": + err = licensecheck.Verify(p) + default: + fmt.Fprintln(os.Stderr, "usage: licensecheck --mode=fetch|verify") + os.Exit(2) + } + if err != nil { + fmt.Fprintf(os.Stderr, "licensecheck: %v\n", err) + os.Exit(1) + } +}