Skip to content

fix: resolve SPDX expressions that use the WITH operator - #555

Open
Eljees wants to merge 1 commit into
anchore:mainfrom
Eljees:agent/spdx-with-exception
Open

fix: resolve SPDX expressions that use the WITH operator#555
Eljees wants to merge 1 commit into
anchore:mainfrom
Eljees:agent/spdx-with-exception

Conversation

@Eljees

@Eljees Eljees commented Jul 28, 2026

Copy link
Copy Markdown

Fixes #500

Problem

spdxexp.ExtractLicenses hands back a WITH expression as a single atom, and the SPDX license list is indexed by license ID alone, so the lookup in handleSPDXLicense can never succeed for it. Probed against the parser and index this repo already uses:

expr="Apache-2.0 WITH LLVM-exception"                       atoms=[Apache-2.0 WITH LLVM-exception]
  lookup("Apache-2.0 WITH LLVM-exception") -> SPDX license Apache-2.0 WITH LLVM-exception not found

expr="Apache-2.0 OR Apache-2.0 WITH LLVM-exception OR MIT"  atoms=[Apache-2.0  Apache-2.0 WITH LLVM-exception  MIT]
  lookup("Apache-2.0")                     -> <nil>
  lookup("Apache-2.0 WITH LLVM-exception") -> SPDX license Apache-2.0 WITH LLVM-exception not found
  lookup("MIT")                            -> <nil>

So every component carrying an exception logs unable to get license by ID and falls through to addNonSPDXLicense, which is the Unknown in the report from the issue.

Change

When the lookup fails and the atom is a WITH expression, retry with the license the exception applies to.

Deliberately narrow:

  • The full expression stays on the resulting License.SPDXExpression. Policy matching goes through License.String(), which returns that expression, so no rule that matches today starts or stops matching.
  • The retry only happens after the direct lookup fails, so nothing that resolves today takes a different path.
  • A trailing + on the license part is trimmed, matching what the surrounding code already does for plain atoms.

What changes is that the license resolves: name, reference, details URL and OSI status now come from the base license instead of being empty. The exception itself is not recorded separately — License has no field for it, and adding one would change the JSON output — but it remains visible in the expression.

Tests

TestConvertSyftLicenses_SPDXExpressionWithException covers Apache-2.0 WITH LLVM-exception and GPL-2.0-only WITH Classpath-exception-2.0.

With grant/license.go reverted to its state on main:

--- FAIL: TestConvertSyftLicenses_SPDXExpressionWithException/Apache-2.0_WITH_LLVM-exception
    license_test.go:81: expected "Apache-2.0 WITH LLVM-exception" to resolve to an SPDX license, got name "Apache-2.0 WITH LLVM-exception"
--- FAIL: TestConvertSyftLicenses_SPDXExpressionWithException/GPL-2.0-only_WITH_Classpath-exception-2.0
--- PASS: TestConvertSyftLicenses_MalformedSPDXExpressionDoesNotPanic

With the change:

ok  	github.com/anchore/grant/grant	17.443s

The SPDX parser returns "<license> WITH <exception>" as a single atom,
but the SPDX license list is indexed by license ID alone, so looking the
atom up always failed. Every affected component logged "unable to get
license by ID" and fell back to an unresolved license, which is what
users see as Unknown.

When the lookup fails and the atom is a WITH expression, retry with the
license the exception applies to. The full expression stays on the
resulting License, so policy matching -- which goes through
License.String() and therefore the SPDX expression -- is unchanged; what
changes is that the license is now resolved and carries its name,
reference and OSI status instead of nothing.

Signed-off-by: Eljees <3.14hell@gmail.com>
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.

Multiple license assigned ( WITH / OR ) operator

1 participant