mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-12 07:40:48 -08:00
fix(license): handle SPDX WITH exceptions as single license in category detection (#9380)
This commit is contained in:
@@ -13,17 +13,16 @@ import (
|
||||
"github.com/aquasecurity/trivy/pkg/set"
|
||||
)
|
||||
|
||||
type ScannerOption struct {
|
||||
IgnoredLicenses []string
|
||||
LicenseCategories map[types.LicenseCategory][]string
|
||||
}
|
||||
|
||||
type Scanner struct {
|
||||
categories map[types.LicenseCategory][]string
|
||||
categories map[types.LicenseCategory]set.Set[string]
|
||||
}
|
||||
|
||||
func NewScanner(categories map[types.LicenseCategory][]string) Scanner {
|
||||
return Scanner{categories: categories}
|
||||
return Scanner{
|
||||
categories: lo.MapValues(categories, func(v []string, _ types.LicenseCategory) set.Set[string] {
|
||||
return set.NewCaseInsensitive(v...)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scanner) Scan(licenseName string) (types.LicenseCategory, string) {
|
||||
@@ -49,6 +48,14 @@ func (s *Scanner) detectCategory(license expression.Expression) types.LicenseCat
|
||||
case expression.SimpleExpr:
|
||||
category = s.licenseToCategory(e)
|
||||
case expression.CompoundExpr:
|
||||
// Detect license category for `WITH` operator as a simple expression
|
||||
if e.Conjunction() == expression.TokenWith {
|
||||
category = s.licenseToCategory(expression.SimpleExpr{
|
||||
License: e.String(),
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
left := s.detectCategory(e.Left())
|
||||
right := s.detectCategory(e.Right())
|
||||
if left == types.CategoryUnknown || right == types.CategoryUnknown {
|
||||
@@ -71,12 +78,12 @@ func (s *Scanner) detectCategory(license expression.Expression) types.LicenseCat
|
||||
// If a match is found, it returns `unknown` category and severity.
|
||||
func (s *Scanner) ScanTextLicense(licenseText string) (types.LicenseCategory, string) {
|
||||
for cat, names := range s.categories {
|
||||
for _, name := range names {
|
||||
if !strings.HasPrefix(name, LicenseTextPrefix) {
|
||||
for name := range names.Iter() {
|
||||
n, ok := strings.CutPrefix(name, LicenseTextPrefix)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
n := strings.TrimPrefix(name, LicenseTextPrefix)
|
||||
match, err := regexp.MatchString(n, licenseText)
|
||||
if err != nil {
|
||||
log.WithPrefix("license").Debug("Failed to match license text", log.String("license_text", licenseText), log.Err(err))
|
||||
@@ -104,11 +111,11 @@ func categoryToSeverity(category types.LicenseCategory) dbTypes.Severity {
|
||||
}
|
||||
|
||||
func (s *Scanner) licenseToCategory(se expression.SimpleExpr) types.LicenseCategory {
|
||||
normalizedNames := set.New(se.String()) // The license name with suffix (e.g. AGPL-1.0-or-later)
|
||||
normalizedNames.Append(se.License) // Also accept the license name without suffix (e.g. AGPL-1.0)
|
||||
normalizedNames := set.NewCaseInsensitive(se.String()) // The license name with suffix (e.g. AGPL-1.0-or-later)
|
||||
normalizedNames.Append(se.License) // Also accept the license name without suffix (e.g. AGPL-1.0)
|
||||
|
||||
for category, names := range s.categories {
|
||||
if normalizedNames.Intersection(set.New(names...)).Size() > 0 {
|
||||
if normalizedNames.Intersection(names).Size() > 0 {
|
||||
return category
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,30 @@ func TestScanner_Scan(t *testing.T) {
|
||||
wantCategory: types.CategoryForbidden,
|
||||
wantSeverity: "CRITICAL",
|
||||
},
|
||||
{
|
||||
name: "license with exception",
|
||||
categories: map[types.LicenseCategory][]string{
|
||||
types.CategoryForbidden: {
|
||||
"GPL-2.0-only WITH Classpath-exception-2.0",
|
||||
expression.GPL30,
|
||||
expression.Apache20,
|
||||
},
|
||||
},
|
||||
licenseName: "GPL-3.0-only OR GPL-2.0-only WITH Classpath-exception-2.0 AND Apache-2.0",
|
||||
wantCategory: types.CategoryForbidden,
|
||||
wantSeverity: "CRITICAL",
|
||||
},
|
||||
{
|
||||
name: "with separator in lowercase",
|
||||
categories: map[types.LicenseCategory][]string{
|
||||
types.CategoryForbidden: {
|
||||
"Similar to Apache License but with the acknowledgment clause removed.",
|
||||
},
|
||||
},
|
||||
licenseName: "Similar to Apache License but with the acknowledgment clause removed.",
|
||||
wantCategory: types.CategoryForbidden,
|
||||
wantSeverity: "CRITICAL",
|
||||
},
|
||||
{
|
||||
name: "compound unknown license",
|
||||
categories: map[types.LicenseCategory][]string{
|
||||
|
||||
Reference in New Issue
Block a user