dennisdoomen / dennisdoomen/packageguard
[Feature]: Support SPDX license expressions (OR, AND, WITH) instead of plain identifiers
- Dominant language
- C#
- Stars
- 73
- Forks
- 3
- Avg merge
- 3h
- Merged PRs (30d)
- 20
Description
### Background and motivation
Licenses are configured and matched as plain SPDX identifiers. But packages regularly declare SPDX *expressions* rather than single identifiers:
- `MIT OR Apache-2.0` — dual licensed, and you get to choose
- `(MIT OR Apache-2.0) AND Unicode-DFS-2016`
- `GPL-2.0-only WITH Classpath-exception-2.0` — the exception is the entire point
- `Apache-2.0 WITH LLVM-exception`
These are common in the npm ecosystem in particular, where `MIT OR Apache-2.0` is close to a default for Rust-adjacent and cross-published packages.
With plain string matching, a package declaring `MIT OR Apache-2.0` fails an allow list containing both MIT and Apache-2.0. That is a false positive on a package that is unambiguously acceptable, and it teaches users to work around the tool by allow-listing package names, which weakens the policy everywhere.
**Proposal**
Parse and evaluate SPDX license expressions properly:
- For `OR`, the package is allowed if *any* operand is allowed, since the consumer chooses.
- For `AND`, the package is allowed only if *every* operand is allowed, since all terms apply simultaneously.
- For `WITH`, evaluate the exception. Treating `GPL-2.0-only WITH Classpath-exception-2.0` identically to bare `GPL-2.0-only` is wrong in a way that matters — the exception exists precisely to make linking acceptable.
- Deny rules should evaluate with inverted logic: a package is denied under `OR` only if *all* operands are denied, because a single acceptable option is enough.
Further points:
- **Configuration should accept expressions too.** Allowing `"GPL-2.0-only WITH Classpath-exception-2.0"` in the allow list lets teams express the exception explicitly rather than allowing GPL wholesale.
- **Deprecated and legacy identifiers.** `GPL-3.0` was split into `GPL-3.0-only` and `GPL-3.0-or-later`. Older packages still use the deprecated forms and they should be normalised rather than silently failing to match.
- **`+` suffix.** `Apache-2.0+` and similar "or later" forms need handling.
- **Reporting.** When a package is allowed via an expression, the report should say which operand satisfied the policy. "Allowed as MIT (from `MIT OR Apache-2.0`)" is far more trustworthy than a bare pass.
- **Risk scoring.** `LegalRiskEvaluator` should score the *least* restrictive operand of an `OR`, not the most restrictive, otherwise dual-licensed packages are penalised for offering a choice.
There are existing SPDX expression parsers, though depending on one needs weighing against the license and size cost of the dependency — a modest hand-rolled parser for this grammar is not unreasonable.
### Alternative Concerns
- Users can add the exact expression string to the allow list as a literal, which works but is brittle and does not generalise across the many combinations found in the wild.
- Splitting on `OR` with a naive string operation handles the most common case but breaks on parentheses and on `WITH`.
### Are you willing help with a pull-request?
No
Contributor guide
Research direction
Start by locating the existing plain-identifier matching and configuration paths, then inspect LegalRiskEvaluator and the reporting flow. Done means SPDX OR, AND, and WITH expressions, deprecated forms, and + suffixes are evaluated for allow and deny rules, with the satisfying operand reported and risk scoring following the least restrictive OR operand.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- security
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100