Skip to content

fix: match forbidden licenses by family, not exact string - #24

Merged
Hinne1 merged 1 commit into
mainfrom
claude/forbidden-license-family-matching
Aug 3, 2026
Merged

fix: match forbidden licenses by family, not exact string#24
Hinne1 merged 1 commit into
mainfrom
claude/forbidden-license-family-matching

Conversation

@Hinne1

@Hinne1 Hinne1 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

The bug

Both validators compared each forbidden entry against the found-license array with:

jq -e --arg license "$forbidden" 'index($license) != null'

index() on an array is exact element equality. With the org-wide FORBIDDEN_LICENSES="GPL;AGPL", that blocks only a license literally named GPL — which no real dependency carries. Measured against the current implementation:

found                          GPL      AGPL
"GPL-3.0-only"                 passes   passes
"AGPL-3.0"                     passes   passes
"Apache-2.0 AND GPL-2.0"       passes   passes
"GPL"                          BLOCKED  passes

Every real SPDX identifier sails through. The gate has been effectively inert for its configured value.

Found while auditing warden and council for commercial redistribution — those closures happen to be clean (all permissive, verified independently), so nothing has slipped past. But the gate would not have caught it if something had.

The fix

An entry now names a family: it matches an identifier that equals it, or continues it after -, . or +, case-insensitively — after the SPDX expression is split on AND / OR / WITH and parentheses, since a found value is an expression rather than a bare identifier.

Family, not substring, deliberately. GPL does not start blocking LGPL-3.0 and AGPL-3.0 — those are different licenses that merely contain the same letters, and a project that listed GPL has not thereby made a decision about them. Callers wanting them list them (GPL;LGPL;AGPL). This is the one judgement call in the change and the easiest to reverse if the org wants the broader reading.

The identifiers the READMEs document (GPL-3.0;AGPL-3.0;SSPL-1.0) keep working, and now also match the modern -only / -or-later spellings they predate.

Both validators

npm-license-validator carried the identical block and the identical bug. Fixed in the same way.

Test

The repo had no tests. test/forbidden-matching_test.sh extracts the block from the shipped action.yml rather than restating the logic, so it cannot keep passing after the code it claims to cover has changed. Wired into lint.yml as a forbidden-license matching job.

Verified it is a real regression test, not a vacuous one:

  • 28/28 pass against this branch (14 cases × both validators)
  • 18/28 fail against the exact-match implementation on main

Cases cover: family matching, LGPL/AGPL non-matching, composite AND/OR/WITH expressions, the documented full identifiers, case-insensitivity, the permissive-set and empty-list pass paths, and multi-family reporting.

Also verified locally: shellcheck --severity=info clean on the new script, and the repo's own run-block shellcheck passes on both patched action.yml files.

Note

Repos relying on this gate have been getting less protection than the configuration implied. Worth a re-run on enrolled repos once this merges — a previously green License Check is not evidence of a clean closure.

The check compared each forbidden entry against the found-license array
with `jq 'index($license)'`, which is exact element equality. With the
org's `FORBIDDEN_LICENSES="GPL;AGPL"` that blocked only a license
literally named "GPL" — no real dependency is. Every actual SPDX
identifier passed: GPL-3.0-only, AGPL-3.0, and any composite such as
"Apache-2.0 AND GPL-2.0". Both validators had it.

An entry now names a family: it matches an identifier that equals it or
continues it after `-`, `.` or `+`, case-insensitively, after the SPDX
expression is split on AND / OR / WITH and parentheses.

Family, not substring, so "GPL" does not silently start blocking LGPL
and AGPL — those are different licenses, and a project that listed GPL
has not thereby decided about them. Callers wanting them list them.

test/forbidden-matching_test.sh extracts the block from the shipped
action.yml rather than restating it, so it cannot keep passing after the
code it covers has changed. It fails 18 of its 28 cases against the
exact-match implementation.
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@Hinne1
Hinne1 marked this pull request as ready for review August 3, 2026 08:57
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@Hinne1
Hinne1 merged commit d1902f0 into main Aug 3, 2026
7 checks passed
@Hinne1
Hinne1 deleted the claude/forbidden-license-family-matching branch August 3, 2026 09:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant