Compare commits

...
9 Commits
Author SHA1 Message Date
dependabot[bot] ab9db0024e chore(deps): bump zizmorcore/zizmor-action from 0.1.1 to 0.1.2 (#4135)
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.1.1 to 0.1.2.
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/f52a838cfabf134edcbaa7c8b3677dde20045018...5ca5fc7a4779c5263a3ffa0e1f693009994446d1)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-13 10:07:03 -04:00
Christopher Angelo Phillips 6b48bd4b5e feat: add package supplier flag (#4131)
---------

Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2025-08-12 14:49:41 -04:00
Christopher Angelo Phillips 89470ecdd3 feat: update syft license construction to be able to look up by URL (#4132)
---------
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
2025-08-12 14:30:32 -04:00
dependabot[bot] 104df88143 chore(deps): bump github/codeql-action from 3.29.8 to 3.29.9 (#4134)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.8 to 3.29.9.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/76621b61decf072c1cee8dd1ce2d2a82d33c17ed...df559355d593797519d70b90fc8edd5db049e7a2)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.29.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 18:23:39 +00:00
honigbot 80e61175ad fix: support multiple letters in openssl patch version (#4106)
Signed-off-by: honigbot <thesoftbear@gmail.com>
2025-08-12 10:30:41 -04:00
Keith Zantow 9f956dca8f fix: closed reader during java binary detection (#4129)
Signed-off-by: Keith Zantow <kzantow@gmail.com>
2025-08-12 08:58:28 -04:00
dependabot[bot] 6452a19009 chore(deps): bump actions/checkout from 4.2.2 to 5.0.0 (#4130)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.2 to 5.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/11bd71901bbe5b1630ceea73d27597364c9af683...08c6903cd8c0fde910a37f88322edcfb5dd907a8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-11 16:54:59 -04:00
Emmanuel Ferdman 21496e7a81 chore: update GoReleaser configurations (#4128)
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
2025-08-11 09:38:33 -04:00
anchore-actions-token-generator[bot]andwagoodman 3e5befc267 chore(deps): update CPE dictionary index (#4126)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: wagoodman <590471+wagoodman@users.noreply.github.com>
2025-08-10 23:48:24 -04:00
63 changed files with 5112 additions and 193 deletions
+4 -4
View File
@@ -36,7 +36,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
@@ -47,7 +47,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed #v3.29.5
uses: github/codeql-action/init@df559355d593797519d70b90fc8edd5db049e7a2 #v3.29.5
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -58,7 +58,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@76621b61decf072c1cee8dd1ce2d2a82d33c17ed #v3.29.5
uses: github/codeql-action/autobuild@df559355d593797519d70b90fc8edd5db049e7a2 #v3.29.5
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -72,4 +72,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@76621b61decf072c1cee8dd1ce2d2a82d33c17ed #v3.29.5
uses: github/codeql-action/analyze@df559355d593797519d70b90fc8edd5db049e7a2 #v3.29.5
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
issues: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
+2 -2
View File
@@ -15,7 +15,7 @@ jobs:
environment: release
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
@@ -116,7 +116,7 @@ jobs:
# required for goreleaser signs section with cosign
id-token: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
fetch-depth: 0
persist-credentials: true
@@ -19,7 +19,7 @@ jobs:
permissions:
packages: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
if: github.repository_owner == 'anchore' # only run for main repo (not forks)
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
if: github.repository == 'anchore/syft' # only run for main repo
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
if: github.repository == 'anchore/syft' # only run for main repo
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
@@ -23,12 +23,12 @@ jobs:
contents: read
security-events: write # for uploading SARIF results
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: "Run zizmor"
uses: zizmorcore/zizmor-action@f52a838cfabf134edcbaa7c8b3677dde20045018 # v0.1.1
uses: zizmorcore/zizmor-action@5ca5fc7a4779c5263a3ffa0e1f693009994446d1 # v0.1.2
with:
config-file: .github/zizmor.yml
sarif-upload: true
+9 -9
View File
@@ -17,7 +17,7 @@ jobs:
name: "Static analysis"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
@@ -34,7 +34,7 @@ jobs:
# we need more storage than what's on the default runner
runs-on: ubuntu-22.04-4core-16gb
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
@@ -52,7 +52,7 @@ jobs:
name: "Integration tests"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
@@ -72,7 +72,7 @@ jobs:
name: "Build snapshot artifacts"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
@@ -107,7 +107,7 @@ jobs:
needs: [Build-Snapshot-Artifacts]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
@@ -164,7 +164,7 @@ jobs:
needs: [Build-Snapshot-Artifacts]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
@@ -212,7 +212,7 @@ jobs:
- name: Install Cosign
uses: sigstore/cosign-installer@d58896d6a1865668819e1d91763c7751a165e159 # v3.9.2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
@@ -251,7 +251,7 @@ jobs:
needs: [Build-Snapshot-Artifacts]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
@@ -291,7 +291,7 @@ jobs:
- Cli-Linux
- Upload-Snapshot-Artifacts
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
with:
persist-credentials: false
+5 -4
View File
@@ -48,12 +48,13 @@ builds:
archives:
- id: linux-archives
builds: [linux-build]
ids: [linux-build]
- id: darwin-archives
builds: [darwin-build]
ids: [darwin-build]
- id: windows-archives
format: zip
builds: [windows-build]
formats:
- zip
ids: [windows-build]
nfpms:
- license: "Apache 2.0"
+3 -2
View File
@@ -218,8 +218,9 @@ func getSource(ctx context.Context, opts *options.Catalog, userInput string, sou
cfg := syft.DefaultGetSourceConfig().
WithRegistryOptions(opts.Registry.ToOptions()).
WithAlias(source.Alias{
Name: opts.Source.Name,
Version: opts.Source.Version,
Name: opts.Source.Name,
Version: opts.Source.Version,
Supplier: opts.Source.Supplier,
}).
WithExcludeConfig(source.ExcludeConfig{
Paths: opts.Exclusions,
+3
View File
@@ -259,6 +259,9 @@ func (cfg *Catalog) AddFlags(flags clio.FlagSet) {
flags.StringVarP(&cfg.Source.BasePath, "base-path", "",
"base directory for scanning, no links will be followed above this directory, and all paths will be reported relative to this directory")
flags.StringVarP(&cfg.Source.Supplier, "source-supplier", "",
"the organization that supplied the component, which often may be the manufacturer, distributor, or repackager")
}
func (cfg *Catalog) DescribeFields(descriptions fangs.FieldDescriptionSet) {
+2 -2
View File
@@ -43,9 +43,9 @@ over to the final syft 1.0 json output this option can be used to ease the trans
Note: long term support for this option is not guaranteed (it may change or break at any time)`)
descriptions.Add(&o.Template.Path, `path to the template file to use when rendering the output with the template output format.
descriptions.Add(&o.Template.Path, `path to the template file to use when rendering the output with the template output format.
Note that all template paths are based on the current syft-json schema`)
descriptions.Add(&o.Template.Legacy, `if true, uses the go structs for the syft-json format for templating.
descriptions.Add(&o.Template.Legacy, `if true, uses the go structs for the syft-json format for templating.
if false, uses the syft-json output for templating (which follows the syft JSON schema exactly).
Note: long term support for this option is not guaranteed (it may change or break at any time)`)
+2
View File
@@ -16,6 +16,8 @@ import (
type sourceConfig struct {
Name string `json:"name" yaml:"name" mapstructure:"name"`
Version string `json:"version" yaml:"version" mapstructure:"version"`
Supplier string `json:"supplier" yaml:"supplier" mapstructure:"supplier"`
Source string `json:"source" yaml:"source" mapstructure:"source"`
BasePath string `yaml:"base-path" json:"base-path" mapstructure:"base-path"` // specify base path for all file paths
File fileSource `json:"file" yaml:"file" mapstructure:"file"`
Image imageSource `json:"image" yaml:"image" mapstructure:"image"`
@@ -78,7 +78,10 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
for _, image := range images {
originalSBOM, _ := catalogFixtureImage(t, image, source.SquashedScope)
// we need a way to inject supplier into this test
// supplier is not available as part of the SBOM Config API since the flag
// is used in conjunction with the SourceConfig which is injected into generateSBOM during scan
originalSBOM.Source.Supplier = "anchore"
f := encoders.GetByString(test.name)
require.NotNil(t, f)
+1 -1
View File
@@ -3,5 +3,5 @@ package internal
const (
// JSONSchemaVersion is the current schema version output by the JSON encoder
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
JSONSchemaVersion = "16.0.36"
JSONSchemaVersion = "16.0.37"
)
@@ -31,6 +31,13 @@ var licenseIDs = map[string]string{
{{ printf "%q" $k }}: {{ printf "%q" $v }},
{{- end }}
}
// urlToLicense maps license URLs from the seeAlso field to license IDs
var urlToLicense = map[string]string{
{{- range $url, $id := .URLToLicense }}
{{ printf "%q" $url }}: {{ printf "%q" $id }},
{{- end }}
}
`))
var versionMatch = regexp.MustCompile(`([0-9]+)\.?([0-9]+)?\.?([0-9]+)?\.?`)
@@ -68,17 +75,20 @@ func run() error {
}()
licenseIDs := processSPDXLicense(result)
urlToLicense := buildURLToLicenseMap(result)
err = tmp.Execute(f, struct {
Timestamp time.Time
URL string
Version string
LicenseIDs map[string]string
Timestamp time.Time
URL string
Version string
LicenseIDs map[string]string
URLToLicense map[string]string
}{
Timestamp: time.Now(),
URL: url,
Version: result.Version,
LicenseIDs: licenseIDs,
Timestamp: time.Now(),
URL: url,
Version: result.Version,
LicenseIDs: licenseIDs,
URLToLicense: urlToLicense,
})
if err != nil {
@@ -156,3 +166,30 @@ func cleanLicenseID(id string) string {
cleanID := strings.ToLower(id)
return strings.ReplaceAll(cleanID, "-", "")
}
// buildURLToLicenseMap creates a mapping from license URLs (from seeAlso fields) to license IDs
func buildURLToLicenseMap(result LicenseList) map[string]string {
urlMap := make(map[string]string)
for _, l := range result.Licenses {
// Skip deprecated licenses
if l.Deprecated {
// Find replacement license if available
replacement := result.findReplacementLicense(l)
if replacement != nil {
// Map deprecated license URLs to the replacement license
for _, url := range l.SeeAlso {
urlMap[url] = replacement.ID
}
}
continue
}
// Add URLs from non-deprecated licenses
for _, url := range l.SeeAlso {
urlMap[url] = l.ID
}
}
return urlMap
}
+17
View File
@@ -35,3 +35,20 @@ func cleanLicenseID(id string) string {
id = strings.ToLower(id)
return strings.ReplaceAll(id, "-", "")
}
// LicenseInfo contains license ID and name
type LicenseInfo struct {
ID string
}
// LicenseByURL returns the license ID and name for a given URL from the SPDX license list
// The URL should match one of the URLs in the seeAlso field of an SPDX license
func LicenseByURL(url string) (LicenseInfo, bool) {
url = strings.TrimSpace(url)
if id, exists := urlToLicense[url]; exists {
return LicenseInfo{
ID: id,
}, true
}
return LicenseInfo{}, false
}
File diff suppressed because it is too large Load Diff
+97
View File
@@ -0,0 +1,97 @@
package spdxlicense
import (
"testing"
)
func TestLicenseByURL(t *testing.T) {
tests := []struct {
name string
url string
wantID string
wantFound bool
}{
{
name: "MIT license URL (https)",
url: "https://opensource.org/license/mit/",
wantID: "MIT",
wantFound: true,
},
{
name: "MIT license URL (http)",
url: "http://opensource.org/licenses/MIT",
wantID: "MIT",
wantFound: true,
},
{
name: "Apache 2.0 license URL",
url: "https://www.apache.org/licenses/LICENSE-2.0",
wantID: "Apache-2.0",
wantFound: true,
},
{
name: "GPL 3.0 or later URL",
url: "https://www.gnu.org/licenses/gpl-3.0-standalone.html",
wantID: "GPL-3.0-or-later",
wantFound: true,
},
{
name: "BSD 3-Clause URL",
url: "https://opensource.org/licenses/BSD-3-Clause",
wantID: "BSD-3-Clause",
wantFound: true,
},
{
name: "URL with trailing whitespace",
url: " http://opensource.org/licenses/MIT ",
wantID: "MIT",
wantFound: true,
},
{
name: "Unknown URL",
url: "https://example.com/unknown-license",
wantID: "",
wantFound: false,
},
{
name: "Empty URL",
url: "",
wantID: "",
wantFound: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
info, found := LicenseByURL(tt.url)
if found != tt.wantFound {
t.Errorf("LicenseByURL() found = %v, want %v", found, tt.wantFound)
}
if found {
if info.ID != tt.wantID {
t.Errorf("LicenseByURL() ID = %v, want %v", info.ID, tt.wantID)
}
}
})
}
}
func TestLicenseByURL_DeprecatedLicenses(t *testing.T) {
// Test that deprecated license URLs map to their replacement licenses
// For example, GPL-2.0+ should map to GPL-2.0-or-later
// This test needs actual URLs from deprecated licenses
// We can verify by checking if a deprecated license URL maps to a non-deprecated ID
url := "https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html"
info, found := LicenseByURL(url)
if found {
// Check that we got a valid non-deprecated license ID
if info.ID == "" {
t.Error("Got empty license ID for deprecated license URL")
}
// The ID should be the replacement (GPL-2.0-only or GPL-2.0-or-later)
// depending on the URL
t.Logf("Deprecated license URL mapped to: ID=%s", info.ID)
}
}
+142 -2
View File
@@ -2,7 +2,7 @@
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
targetNamespace="http://cyclonedx.org/schema/spdx"
version="1.0-3.24.0">
version="1.0-3.26.0">
<xs:simpleType name="licenseId">
<xs:restriction base="xs:string">
@@ -162,6 +162,11 @@
<xs:documentation>Any OSI License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="any-OSI-perl-modules">
<xs:annotation>
<xs:documentation>Any OSI License - Perl Modules</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Apache-1.0">
<xs:annotation>
<xs:documentation>Apache License 1.0</xs:documentation>
@@ -307,6 +312,11 @@
<xs:documentation>Boehm-Demers-Weiser GC License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Boehm-GC-without-fee">
<xs:annotation>
<xs:documentation>Boehm-Demers-Weiser GC License (without fee)</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Borceux">
<xs:annotation>
<xs:documentation>Borceux license</xs:documentation>
@@ -812,6 +822,16 @@
<xs:documentation>Creative Commons Public Domain Dedication and Certification</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="CC-PDM-1.0">
<xs:annotation>
<xs:documentation>Creative Commons Public Domain Mark 1.0 Universal</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="CC-SA-1.0">
<xs:annotation>
<xs:documentation>Creative Commons Share Alike 1.0 Generic</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="CC0-1.0">
<xs:annotation>
<xs:documentation>Creative Commons Zero v1.0 Universal</xs:documentation>
@@ -1062,6 +1082,21 @@
<xs:documentation>DOC License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="DocBook-Schema">
<xs:annotation>
<xs:documentation>DocBook Schema License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="DocBook-Stylesheet">
<xs:annotation>
<xs:documentation>DocBook Stylesheet License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="DocBook-XML">
<xs:annotation>
<xs:documentation>DocBook XML License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Dotseqn">
<xs:annotation>
<xs:documentation>Dotseqn License</xs:documentation>
@@ -1267,6 +1302,11 @@
<xs:documentation>GD License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="generic-xts">
<xs:annotation>
<xs:documentation>Generic XTS License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="GFDL-1.1">
<xs:annotation>
<xs:documentation>GNU Free Documentation License v1.1</xs:documentation>
@@ -1527,6 +1567,11 @@
<xs:documentation>hdparm License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="HIDAPI">
<xs:annotation>
<xs:documentation>HIDAPI License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Hippocratic-2.1">
<xs:annotation>
<xs:documentation>Hippocratic License 2.1</xs:documentation>
@@ -1617,6 +1662,11 @@
<xs:documentation>Historical Permission Notice and Disclaimer with MIT disclaimer</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="HPND-Netrek">
<xs:annotation>
<xs:documentation>Historical Permission Notice and Disclaimer - Netrek variant</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="HPND-Pbmplus">
<xs:annotation>
<xs:documentation>Historical Permission Notice and Disclaimer - Pbmplus variant</xs:documentation>
@@ -1712,6 +1762,11 @@
<xs:documentation>Inner Net License v2.0</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="InnoSetup">
<xs:annotation>
<xs:documentation>Inno Setup License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Intel">
<xs:annotation>
<xs:documentation>Intel Open Source License</xs:documentation>
@@ -2052,6 +2107,11 @@
<xs:documentation>Minpack License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="MIPS">
<xs:annotation>
<xs:documentation>MIPS License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="MirOS">
<xs:annotation>
<xs:documentation>The MirOS Licence</xs:documentation>
@@ -2072,6 +2132,11 @@
<xs:documentation>Enlightenment License (e16)</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="MIT-Click">
<xs:annotation>
<xs:documentation>MIT Click License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="MIT-CMU">
<xs:annotation>
<xs:documentation>CMU License</xs:documentation>
@@ -2772,6 +2837,11 @@
<xs:documentation>Ruby License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Ruby-pty">
<xs:annotation>
<xs:documentation>Ruby pty extension license</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="SAX-PD">
<xs:annotation>
<xs:documentation>Sax Public Domain Notice</xs:documentation>
@@ -2807,6 +2877,11 @@
<xs:documentation>Sendmail License 8.23</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Sendmail-Open-Source-1.1">
<xs:annotation>
<xs:documentation>Sendmail Open Source License v1.1</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="SGI-B-1.0">
<xs:annotation>
<xs:documentation>SGI Free Software License B v1.0</xs:documentation>
@@ -2867,6 +2942,11 @@
<xs:documentation>Sleepycat License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="SMAIL-GPL">
<xs:annotation>
<xs:documentation>SMAIL General Public License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="SMLNJ">
<xs:annotation>
<xs:documentation>Standard ML of New Jersey License</xs:documentation>
@@ -3007,6 +3087,11 @@
<xs:documentation>Transitive Grace Period Public Licence 1.0</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="ThirdEye">
<xs:annotation>
<xs:documentation>ThirdEye License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="threeparttable">
<xs:annotation>
<xs:documentation>threeparttable License</xs:documentation>
@@ -3037,6 +3122,11 @@
<xs:documentation>THOR Public License 1.0</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="TrustedQSL">
<xs:annotation>
<xs:documentation>TrustedQSL License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="TTWL">
<xs:annotation>
<xs:documentation>Text-Tabs+Wrap License</xs:documentation>
@@ -3057,6 +3147,11 @@
<xs:documentation>Technische Universitaet Berlin License 2.0</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Ubuntu-font-1.0">
<xs:annotation>
<xs:documentation>Ubuntu Font Licence v1.0</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="UCAR">
<xs:annotation>
<xs:documentation>UCAR License</xs:documentation>
@@ -3172,6 +3267,11 @@
<xs:documentation>Do What The F*ck You Want To Public License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="wwl">
<xs:annotation>
<xs:documentation>WWL License</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="wxWindows">
<xs:annotation>
<xs:documentation>wxWindows Library License</xs:documentation>
@@ -3187,6 +3287,11 @@
<xs:documentation>X11 License Distribution Modification Variant</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="X11-swapped">
<xs:annotation>
<xs:documentation>X11 swapped final paragraphs</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Xdebug-1.03">
<xs:annotation>
<xs:documentation>Xdebug License v 1.03</xs:documentation>
@@ -3358,6 +3463,11 @@
<xs:documentation>Bootloader Distribution Exception</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="CGAL-linking-exception">
<xs:annotation>
<xs:documentation>CGAL Linking Exception</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Classpath-exception-2.0">
<xs:annotation>
<xs:documentation>Classpath exception 2.0</xs:documentation>
@@ -3383,6 +3493,11 @@
<xs:documentation>eCos exception 2.0</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="erlang-otp-linking-exception">
<xs:annotation>
<xs:documentation>Erlang/OTP Linking Exception</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Fawkes-Runtime-exception">
<xs:annotation>
<xs:documentation>Fawkes Runtime Exception</xs:documentation>
@@ -3425,7 +3540,7 @@
</xs:enumeration>
<xs:enumeration value="Gmsh-exception">
<xs:annotation>
<xs:documentation>Gmsh exception&gt;</xs:documentation>
<xs:documentation>Gmsh exception</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="GNAT-exception">
@@ -3448,6 +3563,11 @@
<xs:documentation>GNU JavaMail exception</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="GPL-3.0-389-ds-base-exception">
<xs:annotation>
<xs:documentation>GPL-3.0 389 DS Base Exception</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="GPL-3.0-interface-exception">
<xs:annotation>
<xs:documentation>GPL-3.0 Interface Exception</xs:documentation>
@@ -3478,11 +3598,21 @@
<xs:documentation>GStreamer Exception (2008)</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="harbour-exception">
<xs:annotation>
<xs:documentation>harbour exception</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="i2p-gpl-java-exception">
<xs:annotation>
<xs:documentation>i2p GPL+Java Exception</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Independent-modules-exception">
<xs:annotation>
<xs:documentation>Independent Module Linking exception</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="KiCad-libraries-exception">
<xs:annotation>
<xs:documentation>KiCad Libraries Exception</xs:documentation>
@@ -3528,6 +3658,11 @@
<xs:documentation>Macros and Inline Functions Exception</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="mxml-exception">
<xs:annotation>
<xs:documentation>mxml Exception</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="Nokia-Qt-exception-1.1">
<xs:annotation>
<xs:documentation>Nokia Qt LGPL exception 1.1</xs:documentation>
@@ -3583,6 +3718,11 @@
<xs:documentation>Qwt exception 1.0</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="romic-exception">
<xs:annotation>
<xs:documentation>Romic Exception</xs:documentation>
</xs:annotation>
</xs:enumeration>
<xs:enumeration value="RRDtool-FLOSS-exception-2.0">
<xs:annotation>
<xs:documentation>RRDtool FLOSS exception 2.0</xs:documentation>
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "anchore.io/schema/syft/json/16.0.36/document",
"$id": "anchore.io/schema/syft/json/16.0.37/document",
"$ref": "#/$defs/Document",
"$defs": {
"AlpmDbEntry": {
@@ -3047,6 +3047,9 @@
"version": {
"type": "string"
},
"supplier": {
"type": "string"
},
"type": {
"type": "string"
},
@@ -48,7 +48,7 @@ func ToFormatModel(s sbom.SBOM) *cyclonedx.BOM {
packages := s.Artifacts.Packages.Sorted()
components := make([]cyclonedx.Component, len(packages))
for i, p := range packages {
components[i] = helpers.EncodeComponent(p, locationSorter)
components[i] = helpers.EncodeComponent(p, s.Source.Supplier, locationSorter)
}
components = append(components, toOSComponent(s.Artifacts.LinuxDistribution)...)
@@ -220,11 +220,22 @@ func toBomDescriptor(name, version string, srcMetadata source.Description) *cycl
},
},
},
Supplier: toBomSupplier(srcMetadata),
Properties: toBomProperties(srcMetadata),
Component: toBomDescriptorComponent(srcMetadata),
}
}
func toBomSupplier(srcMetadata source.Description) *cyclonedx.OrganizationalEntity {
if srcMetadata.Supplier != "" {
return &cyclonedx.OrganizationalEntity{
Name: srcMetadata.Supplier,
}
}
return nil
}
// used to indicate that a relationship listed under the syft artifact package can be represented as a cyclonedx dependency.
// NOTE: CycloneDX provides the ability to describe components and their dependency on other components.
// The dependency graph is capable of representing both direct and transitive relationships.
@@ -321,10 +332,11 @@ func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Compone
log.Debugf("unable to get fingerprint of source image metadata=%s: %+v", metadata.ID, err)
}
return &cyclonedx.Component{
BOMRef: string(bomRef),
Type: cyclonedx.ComponentTypeContainer,
Name: name,
Version: version,
BOMRef: string(bomRef),
Type: cyclonedx.ComponentTypeContainer,
Name: name,
Version: version,
Supplier: toBomSupplier(srcMetadata),
}
case source.DirectoryMetadata:
if name == "" {
@@ -337,9 +349,10 @@ func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Compone
return &cyclonedx.Component{
BOMRef: string(bomRef),
// TODO: this is lossy... we can't know if this is a file or a directory
Type: cyclonedx.ComponentTypeFile,
Name: name,
Version: version,
Type: cyclonedx.ComponentTypeFile,
Name: name,
Version: version,
Supplier: toBomSupplier(srcMetadata),
}
case source.FileMetadata:
if name == "" {
@@ -352,9 +365,10 @@ func toBomDescriptorComponent(srcMetadata source.Description) *cyclonedx.Compone
return &cyclonedx.Component{
BOMRef: string(bomRef),
// TODO: this is lossy... we can't know if this is a file or a directory
Type: cyclonedx.ComponentTypeFile,
Name: name,
Version: version,
Type: cyclonedx.ComponentTypeFile,
Name: name,
Version: version,
Supplier: toBomSupplier(srcMetadata),
}
}
@@ -389,7 +389,69 @@ func Test_toBomDescriptor(t *testing.T) {
Name: "syft:image:labels:key1",
Value: "value1",
},
}},
},
},
},
{
name: "with optional supplier is on the root component and bom metadata",
args: args{
name: "test-image",
version: "1.0.0",
srcMetadata: source.Description{
Name: "test-image",
Version: "1.0.0",
Supplier: "optional-supplier",
Metadata: source.ImageMetadata{},
},
},
want: &cyclonedx.Metadata{
Timestamp: "",
Lifecycles: nil,
Tools: &cyclonedx.ToolsChoice{
Components: &[]cyclonedx.Component{
{
Type: cyclonedx.ComponentTypeApplication,
Author: "anchore",
Name: "test-image",
Version: "1.0.0",
},
},
},
Authors: nil,
Component: &cyclonedx.Component{
BOMRef: "",
MIMEType: "",
Type: "container",
Supplier: &cyclonedx.OrganizationalEntity{
Name: "optional-supplier",
},
Author: "",
Publisher: "",
Group: "",
Name: "test-image",
Version: "1.0.0",
Description: "",
Scope: "",
Hashes: nil,
Licenses: nil,
Copyright: "",
CPE: "",
PackageURL: "",
SWID: nil,
Modified: nil,
Pedigree: nil,
ExternalReferences: nil,
Properties: nil,
Components: nil,
Evidence: nil,
ReleaseNotes: nil,
},
Manufacture: nil,
Supplier: &cyclonedx.OrganizationalEntity{
Name: "optional-supplier",
},
Licenses: nil,
},
},
}
for _, tt := range tests {
@@ -257,13 +257,11 @@ func toRootPackage(s source.Description) *spdx.Package {
PackageChecksums: checksums,
PackageExternalReferences: nil,
PrimaryPackagePurpose: purpose,
PackageSupplier: &spdx.Supplier{
Supplier: helpers.NOASSERTION,
},
PackageCopyrightText: helpers.NOASSERTION,
PackageDownloadLocation: helpers.NOASSERTION,
PackageLicenseConcluded: helpers.NOASSERTION,
PackageLicenseDeclared: helpers.NOASSERTION,
PackageSupplier: toSPDXSupplier(s),
PackageCopyrightText: helpers.NOASSERTION,
PackageDownloadLocation: helpers.NOASSERTION,
PackageLicenseConcluded: helpers.NOASSERTION,
PackageLicenseDeclared: helpers.NOASSERTION,
}
if purl != nil {
@@ -327,6 +325,23 @@ func toSPDXID(identifiable artifact.Identifiable) spdx.ElementID {
return spdx.ElementID(helpers.SanitizeElementID(id))
}
func toSPDXSupplier(s source.Description) *spdx.Supplier {
supplier := helpers.NOASSERTION
if s.Supplier != "" {
supplier = s.Supplier
}
supplierType := ""
if supplier != helpers.NOASSERTION {
supplierType = helpers.SUPPLIERORG
}
return &spdx.Supplier{
Supplier: supplier,
SupplierType: supplierType,
}
}
// packages populates all Package Information from the package Collection (see https://spdx.github.io/spdx-spec/3-package-information/)
//
//nolint:funlen
@@ -391,7 +406,7 @@ func toPackages(rels *relationship.Index, catalog *pkg.Collection, sbom sbom.SBO
// 7.6: Package Originator: may have single result for either Person or Organization,
// or NOASSERTION
// Cardinality: optional, one
PackageSupplier: toPackageSupplier(p),
PackageSupplier: toPackageSupplier(p, sbom.Source.Supplier),
PackageOriginator: toPackageOriginator(p),
@@ -556,11 +571,18 @@ func toPackageOriginator(p pkg.Package) *spdx.Originator {
}
}
func toPackageSupplier(p pkg.Package) *spdx.Supplier {
func toPackageSupplier(p pkg.Package, sbomSupplier string) *spdx.Supplier {
kind, supplier := helpers.Supplier(p)
if kind == "" || supplier == "" {
supplier := helpers.NOASSERTION
supplierType := ""
if sbomSupplier != "" {
supplier = sbomSupplier
supplierType = helpers.SUPPLIERORG
}
return &spdx.Supplier{
Supplier: helpers.NOASSERTION,
Supplier: supplier,
SupplierType: supplierType,
}
}
return &spdx.Supplier{
@@ -36,8 +36,9 @@ func Test_toFormatModel(t *testing.T) {
name: "container",
in: sbom.SBOM{
Source: source.Description{
Name: "alpine",
Version: "sha256:d34db33f",
Name: "alpine",
Version: "sha256:d34db33f",
Supplier: "Alpine Linux",
Metadata: source.ImageMetadata{
UserInput: "alpine:latest",
ManifestDigest: "sha256:d34db33f",
@@ -61,7 +62,8 @@ func Test_toFormatModel(t *testing.T) {
PackageName: "pkg-1",
PackageVersion: "version-1",
PackageSupplier: &spdx.Supplier{
Supplier: "NOASSERTION",
Supplier: "Alpine Linux",
SupplierType: "Organization",
},
},
{
@@ -78,7 +80,8 @@ func Test_toFormatModel(t *testing.T) {
},
},
PackageSupplier: &spdx.Supplier{
Supplier: "NOASSERTION",
Supplier: "Alpine Linux",
SupplierType: "Organization",
},
},
},
@@ -145,10 +145,21 @@ func containerSource(p *spdx.Package) source.Description {
c := p.PackageChecksums[0]
digest = fmt.Sprintf("%s:%s", fromChecksumAlgorithm(c.Algorithm), c.Value)
}
supplier := ""
if p.PackageSupplier != nil {
// we also don't want NOASSERTION transferred to the syft format
// NOASSERTION == ""
if p.PackageSupplier.Supplier != helpers.NOASSERTION && p.PackageSupplier.SupplierType == helpers.SUPPLIERORG {
supplier = p.PackageSupplier.Supplier
}
}
return source.Description{
ID: id,
Name: p.PackageName,
Version: p.PackageVersion,
ID: id,
Name: p.PackageName,
Version: p.PackageVersion,
Supplier: supplier,
Metadata: source.ImageMetadata{
UserInput: container,
ID: id,
@@ -179,10 +190,16 @@ func fileSource(p *spdx.Package) source.Description {
metadata, version = fileSourceMetadata(p)
}
supplier := ""
if p.PackageSupplier.Supplier != helpers.NOASSERTION {
supplier = p.PackageSupplier.Supplier
}
return source.Description{
ID: string(p.PackageSPDXIdentifier),
Name: p.PackageName,
Version: version,
Supplier: supplier,
Metadata: metadata,
}
}
@@ -525,17 +525,19 @@ func Test_convertToAndFromFormat(t *testing.T) {
UserInput: "some-image:some-tag",
ManifestDigest: "sha256:ab8b83234bc28f28d8e",
},
Name: "some-image",
Version: "some-tag",
Name: "some-image",
Version: "some-tag",
Supplier: "some-supplier",
},
packages: packages,
relationships: relationships,
},
{
name: ". directory source",
name: ". directory source with supplier",
source: source.Description{
ID: "DocumentRoot-Directory-.",
Name: ".",
ID: "DocumentRoot-Directory-.",
Name: ".",
Supplier: "some-supplier",
Metadata: source.DirectoryMetadata{
Path: ".",
},
@@ -544,7 +546,7 @@ func Test_convertToAndFromFormat(t *testing.T) {
relationships: relationships,
},
{
name: "directory source",
name: "directory source without supplier",
source: source.Description{
ID: "DocumentRoot-Directory-my-app",
Name: "my-app",
@@ -14,7 +14,7 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func EncodeComponent(p pkg.Package, locationSorter func(a, b file.Location) int) cyclonedx.Component {
func EncodeComponent(p pkg.Package, supplier string, locationSorter func(a, b file.Location) int) cyclonedx.Component {
props := EncodeProperties(p, "syft:package")
if p.Metadata != nil {
@@ -49,6 +49,7 @@ func EncodeComponent(p pkg.Package, locationSorter func(a, b file.Location) int)
Name: p.Name,
Group: encodeGroup(p),
Version: p.Version,
Supplier: encodeSupplier(p, supplier),
PackageURL: p.PURL,
Licenses: encodeLicenses(p),
CPE: encodeSingleCPE(p),
@@ -61,6 +62,16 @@ func EncodeComponent(p pkg.Package, locationSorter func(a, b file.Location) int)
}
}
// TODO: we eventually want to update this so that we can read "supplier" from different syft metadata
func encodeSupplier(_ pkg.Package, sbomSupplier string) *cyclonedx.OrganizationalEntity {
if sbomSupplier != "" {
return &cyclonedx.OrganizationalEntity{
Name: sbomSupplier,
}
}
return nil
}
func DeriveBomRef(p pkg.Package) string {
// try and parse the PURL if possible and append syft id to it, to make
// the purl unique in the BOM.
@@ -152,7 +152,8 @@ func Test_encodeComponentProperties(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := EncodeComponent(test.input, file.LocationSorter(nil))
sbomSupplier := ""
c := EncodeComponent(test.input, sbomSupplier, file.LocationSorter(nil))
if test.expected == nil {
if c.Properties != nil {
t.Fatalf("expected no properties, got: %+v", *c.Properties)
@@ -212,7 +213,8 @@ func Test_encodeCompomentType(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.pkg.ID()
p := EncodeComponent(tt.pkg, file.LocationSorter(nil))
sbomSupplier := ""
p := EncodeComponent(tt.pkg, sbomSupplier, file.LocationSorter(nil))
assert.Equal(t, tt.want, p)
})
}
@@ -211,6 +211,16 @@ func extractComponents(meta *cyclonedx.Metadata) source.Description {
}
c := meta.Component
supplier := ""
// First check component-level supplier
if c.Supplier != nil && c.Supplier.Name != "" {
supplier = c.Supplier.Name
}
// Fall back to metadata-level supplier if component supplier is not set
if supplier == "" && meta.Supplier != nil && meta.Supplier.Name != "" {
supplier = meta.Supplier.Name
}
switch c.Type {
case cyclonedx.ComponentTypeContainer:
var labels map[string]string
@@ -220,7 +230,8 @@ func extractComponents(meta *cyclonedx.Metadata) source.Description {
}
return source.Description{
ID: "",
ID: "",
Supplier: supplier,
// TODO: can we decode alias name-version somehow? (it isn't be encoded in the first place yet)
Metadata: source.ImageMetadata{
@@ -236,6 +247,7 @@ func extractComponents(meta *cyclonedx.Metadata) source.Description {
// TODO: this is lossy... we can't know if this is a file or a directory
return source.Description{
ID: "",
Supplier: supplier,
Metadata: source.FileMetadata{Path: c.Name},
}
}
@@ -62,6 +62,9 @@ func decodeLicenses(c *cyclonedx.Component) []pkg.License {
licenses = append(licenses, pkg.NewLicenseFromURLsWithContext(context.TODO(), l.License.ID, l.License.URL))
case l.License != nil && l.License.Name != "":
licenses = append(licenses, pkg.NewLicenseFromURLsWithContext(context.TODO(), l.License.Name, l.License.URL))
case l.License != nil && l.License.URL != "":
// Try to enrich license from URL when ID and Name are empty
licenses = append(licenses, pkg.NewLicenseFromURLsWithContext(context.TODO(), "", l.License.URL))
case l.Expression != "":
licenses = append(licenses, pkg.NewLicenseWithContext(context.TODO(), l.Expression))
default:
@@ -163,10 +166,18 @@ func processCustomLicense(l pkg.License) cyclonedx.Licenses {
func processLicenseURLs(l pkg.License, spdxID string, populate *cyclonedx.Licenses) {
for _, url := range l.URLs {
if spdxID == "" {
// CycloneDX requires either an id or name to be present for a license
// If l.Value is empty, use the URL as the name to ensure schema compliance
// at this point we've already tried to enrich the license we just don't want the format
// conversion to be lossy here
name := l.Value
if name == "" {
name = url
}
*populate = append(*populate, cyclonedx.LicenseChoice{
License: &cyclonedx.License{
URL: url,
Name: l.Value,
Name: name,
},
})
} else {
@@ -121,6 +121,22 @@ func Test_encodeLicense(t *testing.T) {
},
},
},
{
name: "with URL only and no name or SPDX ID",
input: pkg.Package{
Licenses: pkg.NewLicenseSet(
pkg.NewLicenseFromURLsWithContext(ctx, "", "http://jaxen.codehaus.org/license.html"),
),
},
expected: &cyclonedx.Licenses{
{
License: &cyclonedx.License{
Name: "http://jaxen.codehaus.org/license.html",
URL: "http://jaxen.codehaus.org/license.html",
},
},
},
},
{
name: "with multiple values licenses are deduplicated",
input: pkg.Package{
@@ -10,6 +10,7 @@ import (
const NONE = "NONE"
const NOASSERTION = "NOASSERTION"
const SUPPLIERORG = "Organization"
func DownloadLocation(p pkg.Package) string {
// 3.7: Package Download Location
+8
View File
@@ -12,10 +12,16 @@ import (
)
// Source object represents the thing that was cataloged
// Note: syft currently makes no claims or runs any logic to determine the Supplier field below
// Instead, the Supplier can be determined by the user of syft and passed as a config or flag to help fulfill
// the NTIA minimum elements. For mor information see the NTIA framing document below
// https://www.ntia.gov/files/ntia/publications/framingsbom_20191112.pdf
type Source struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Supplier string `json:"supplier,omitempty"`
Type string `json:"type"`
Metadata interface{} `json:"metadata"`
}
@@ -25,6 +31,7 @@ type sourceUnpacker struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Version string `json:"version"`
Supplier string `json:"supplier,omitempty"`
Type string `json:"type"`
Metadata json.RawMessage `json:"metadata"`
Target json.RawMessage `json:"target"` // pre-v9 schema support
@@ -40,6 +47,7 @@ func (s *Source) UnmarshalJSON(b []byte) error {
s.Name = unpacker.Name
s.Version = unpacker.Version
s.Supplier = unpacker.Supplier
s.Type = unpacker.Type
s.ID = unpacker.ID
+1
View File
@@ -320,6 +320,7 @@ func toSourceModel(src source.Description) model.Source {
ID: src.ID,
Name: src.Name,
Version: src.Version,
Supplier: src.Supplier,
Type: sourcemetadata.JSONName(src.Metadata),
Metadata: src.Metadata,
}
+18 -14
View File
@@ -56,19 +56,21 @@ func Test_toSourceModel(t *testing.T) {
{
name: "directory",
src: source.Description{
ID: "test-id",
Name: "some-name",
Version: "some-version",
ID: "test-id",
Name: "some-name",
Version: "some-version",
Supplier: "optional-supplier",
Metadata: source.DirectoryMetadata{
Path: "some/path",
Base: "some/base",
},
},
expected: model.Source{
ID: "test-id",
Name: "some-name",
Version: "some-version",
Type: "directory",
ID: "test-id",
Name: "some-name",
Version: "some-version",
Supplier: "optional-supplier",
Type: "directory",
Metadata: source.DirectoryMetadata{
Path: "some/path",
Base: "some/base",
@@ -78,9 +80,10 @@ func Test_toSourceModel(t *testing.T) {
{
name: "file",
src: source.Description{
ID: "test-id",
Name: "some-name",
Version: "some-version",
ID: "test-id",
Name: "some-name",
Version: "some-version",
Supplier: "optional-supplier",
Metadata: source.FileMetadata{
Path: "some/path",
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}},
@@ -88,10 +91,11 @@ func Test_toSourceModel(t *testing.T) {
},
},
expected: model.Source{
ID: "test-id",
Name: "some-name",
Version: "some-version",
Type: "file",
ID: "test-id",
Name: "some-name",
Version: "some-version",
Supplier: "optional-supplier",
Type: "file",
Metadata: source.FileMetadata{
Path: "some/path",
Digests: []file.Digest{{Algorithm: "sha256", Value: "some-digest"}},
+1
View File
@@ -315,6 +315,7 @@ func toSyftSourceData(s model.Source) *source.Description {
ID: s.ID,
Name: s.Name,
Version: s.Version,
Supplier: s.Supplier,
Metadata: s.Metadata,
}
}
+29
View File
@@ -4,6 +4,7 @@ package license
import (
"fmt"
"runtime/debug"
"strings"
"github.com/github/go-spdx/v2/spdxexp"
@@ -17,6 +18,18 @@ const (
Concluded Type = "concluded"
)
// trimFileSuffix removes common file extensions from the end of a string
func trimFileSuffix(s string) string {
suffixes := []string{".txt", ".pdf", ".html", ".htm", ".md", ".markdown", ".rst", ".doc", ".docx", ".rtf", ".tex", ".xml", ".json"}
lower := strings.ToLower(s)
for _, suffix := range suffixes {
if strings.HasSuffix(lower, suffix) {
return s[:len(s)-len(suffix)]
}
}
return s
}
func ParseExpression(expression string) (ex string, err error) {
// https://github.com/anchore/syft/issues/1837
// The current spdx library can panic when parsing some expressions
@@ -28,10 +41,26 @@ func ParseExpression(expression string) (ex string, err error) {
}
}()
// Try with the original expression first
licenseID, exists := spdxlicense.ID(expression)
if exists {
return licenseID, nil
}
// Check if the expression is a URL and try to look it up
if info, found := spdxlicense.LicenseByURL(expression); found {
return info.ID, nil
}
// Try with trimmed file suffix
trimmed := trimFileSuffix(expression)
if trimmed != expression {
// Try as a URL with the trimmed version
if info, found := spdxlicense.LicenseByURL(trimmed); found {
return info.ID, nil
}
}
// If it doesn't exist initially in the SPDX list it might be a more complex expression
// ignored variable is any invalid expressions
// TODO: contribute to spdxexp to expose deprecated license IDs
@@ -775,39 +775,6 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
Metadata: metadata("java-binary-oracle", "java"),
},
},
{
logicalFixture: "java-jre-ibm/1.8.0_391/linux-amd64",
expected: pkg.Package{
Name: "java",
Version: "1.8.0-foreman_2023_10_12_13_27-b00",
Type: "binary",
PURL: "pkg:generic/ibm/java@1.8.0-foreman_2023_10_12_13_27-b00",
Locations: locations("java"),
Metadata: metadata("java-binary-ibm", "java"),
},
},
{
logicalFixture: "java-ibm-8-jre/1.8.0_451/linux-amd64",
expected: pkg.Package{
Name: "java",
Version: "1.8.0-_2025_04_14_02_37-b00",
Type: "binary",
PURL: "pkg:generic/ibm/java@1.8.0-_2025_04_14_02_37-b00",
Locations: locations("java"),
Metadata: metadata("java-binary-ibm", "java"),
},
},
{
logicalFixture: "java-ibm-8-sdk-alpine/1.8.0_321/linux-amd64",
expected: pkg.Package{
Name: "java_sdk",
Version: "1.8.0-foreman_2022_01_20_09_33-b00",
Type: "binary",
PURL: "pkg:generic/ibm/java_sdk@1.8.0-foreman_2022_01_20_09_33-b00",
Locations: locations("jdb"),
Metadata: metadata("java-sdk-binary-ibm", "jdb"),
},
},
{
logicalFixture: "java-jdk-openjdk/21.0.2+13-LTS/linux-amd64",
expected: pkg.Package{
@@ -1216,6 +1183,17 @@ func Test_Cataloger_PositiveCases(t *testing.T) {
Metadata: metadata("openssl-binary"),
},
},
{
logicalFixture: "openssl/1.1.1zb/linux-arm64",
expected: pkg.Package{
Name: "openssl",
Version: "1.1.1zb",
Type: "binary",
PURL: "pkg:generic/openssl@1.1.1zb",
Locations: locations("openssl"),
Metadata: metadata("openssl-binary"),
},
},
{
logicalFixture: "gcc/12.3.0/linux-amd64",
expected: pkg.Package{
+1 -1
View File
@@ -493,7 +493,7 @@ func DefaultClassifiers() []binutils.Classifier {
EvidenceMatcher: m.FileContentsVersionMatcher(
// [NUL]OpenSSL 3.1.4'
// [NUL]OpenSSL 1.1.1w'
`\x00OpenSSL (?P<version>[0-9]+\.[0-9]+\.[0-9]+([a-z]|-alpha[0-9]|-beta[0-9]|-rc[0-9])?)`,
`\x00OpenSSL (?P<version>[0-9]+\.[0-9]+\.[0-9]+([a-z]+|-alpha[0-9]|-beta[0-9]|-rc[0-9])?)`,
),
Package: "openssl",
PURL: mustPURL("pkg:generic/openssl@version"),
+17 -17
View File
@@ -122,6 +122,23 @@ func defaultJavaClassifiers() []binutils.Classifier {
PURL: mustPURL("pkg:generic/oracle/graalvm@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:graalvm_for_jdk:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "jdb-binary-openjdk-zulu",
EvidenceMatcher: binutils.MatchAll(
binutils.MatchPath("**/*zulu*/**"),
binutils.MatchAny(
m.FileContentsVersionMatcher(
// [NUL]jdb[NUL]0.0[NUL]11.0.17+8-LTS[NUL]
`(?m)(java|jdb)\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>[0-9]+[^\x00]+)\x00`),
m.FileContentsVersionMatcher(
// arm64 versions: [NUL]0.0[NUL][NUL][NUL][NUL][NUL]11.0.22+7[NUL][NUL][NUL][NUL][NUL][NUL][NUL]jdb[NUL]
`(?m)\x00(?P<release>[0-9]+[.0-9]*)\x00+(?P<version>[0-9]+[^\x00]+)\x00+(java|jdb)`),
),
),
Package: "zulu",
PURL: mustPURL("pkg:generic/azul/zulu@version"),
CPEs: singleCPE("cpe:2.3:a:azul:zulu:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-jdb-binary-openjdk",
EvidenceMatcher: binutils.MatchAll(
@@ -137,23 +154,6 @@ func defaultJavaClassifiers() []binutils.Classifier {
PURL: mustPURL("pkg:generic/oracle/openjdk@version"),
CPEs: singleCPE("cpe:2.3:a:oracle:openjdk:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "jdb-binary-openjdk-zulu",
EvidenceMatcher: binutils.MatchAll(
binutils.MatchPath("**/*zulu*/**"),
binutils.MatchAny(
m.FileContentsVersionMatcher(
// [NUL]openjdk[NUL]java[NUL]0.0[NUL]11.0.17+8-LTS[NUL]
`(?m)\x00openjdk\x00java\x00(?P<release>[0-9]+[.0-9]*)\x00(?P<version>[0-9]+[^\x00]+)\x00`),
m.FileContentsVersionMatcher(
// arm64 versions: [NUL]0.0[NUL][NUL][NUL][NUL][NUL]11.0.22+7[NUL][NUL][NUL][NUL][NUL][NUL][NUL]openjdk[NUL]java[NUL]
`(?m)\x00(?P<release>[0-9]+[.0-9]*)\x00+(?P<version>[0-9]+[^\x00]+)\x00+openjdk\x00java`),
),
),
Package: "zulu",
PURL: mustPURL("pkg:generic/azul/zulu@version"),
CPEs: singleCPE("cpe:2.3:a:azul:zulu:*:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource),
},
{
Class: "java-sdk-binary-ibm",
EvidenceMatcher: binutils.MatchAll(
@@ -0,0 +1,66 @@
package binary
import (
"testing"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
)
func Test_JavaBinaryImage(t *testing.T) {
tests := []struct {
image string
expected []string
}{
{
image: "image-java-binary",
expected: []string{
"java @ 1.8.0-foreman_2022_09_22_15_30-b00 (/staged/positive/ibm/java)",
"jre @ 19.0.1+10-21 (/staged/positive/oracle-macos/java)",
"openjdk @ 1.8.0_352-b08 (/staged/positive/openjdk/java)",
"openjdk @ 11.0.17+8-LTS (/staged/positive/openjdk-lts/java)",
},
},
{
image: "image-java-zulu-8",
expected: []string{
"zulu @ 1.8.0_462-b08 (/usr/lib/jvm/zulu8-ca-amd64/bin/jdb)",
},
},
{
image: "image-java-zulu-21",
expected: []string{
"zulu @ 21.0.8+9-LTS (/usr/lib/jvm/zulu21-ca-amd64/bin/java)",
},
},
{
image: "image-java-ibm-8",
expected: []string{
"java @ 1.8.0-foreman_2023_10_12_13_27-b00 (/opt/ibm/java/jre/bin/java)",
},
},
{
image: "image-java-ibm-jre-8",
expected: []string{
"java @ 1.8.0-_2025_04_14_02_37-b00 (/opt/ibm/java/jre/bin/java)",
},
},
{
image: "image-java-ibm-sdk-8",
expected: []string{
"java_sdk @ 1.8.0-foreman_2022_01_20_09_33-b00 (/opt/ibm/java/bin/jdb)",
},
},
}
for _, tt := range tests {
t.Run(tt.image, func(t *testing.T) {
c := NewClassifierCataloger(ClassifierCatalogerConfig{
Classifiers: defaultJavaClassifiers(),
})
pkgtest.NewCatalogTester().
WithImageResolver(t, tt.image).
ExpectsPackageStrings(tt.expected).
TestCataloger(t, c)
})
}
}
@@ -187,33 +187,6 @@ from-images:
paths:
- /usr/local/apache2/bin/httpd
- name: java-jre-ibm
version: 1.8.0_391
images:
- ref: ibmjava:8@sha256:05ef6b0f754aa3a8cebcec36260a70c234a217b21240a998604f33459037bc08
platform: linux/amd64
paths:
- /opt/ibm/java/jre/bin/java
- /opt/ibm/java/jre/lib/amd64/jli/libjli.so
- name: java-ibm-8-jre
version: 1.8.0_451
images:
- ref: ibmjava:8-jre@sha256:3588cd1cc9b8646fe03b3b15210e69b1b520f1321f8518b69c0e7013d702fd23
platform: linux/amd64
paths:
- /opt/ibm/java/jre/bin/java
- /opt/ibm/java/jre/lib/amd64/jli/libjli.so
- name: java-ibm-8-sdk-alpine
version: 1.8.0_321
images:
- ref: ibmjava:8-sdk-alpine@sha256:4f8ad2029e78f7b91721745a77fc6011a7c0e09b9edeffb6b20b6ec34a6e63cd
platform: linux/amd64
paths:
- /opt/ibm/java/bin/jdb
- /opt/ibm/java/lib/amd64/jli/libjli.so
- version: 10.6.15
images:
- ref: mariadb:10.6.15@sha256:92d499d9e02e92dc55c8160ef4004aa07f2e835197b18864ed214ca441e0dcfc
@@ -0,0 +1 @@
FROM anchore/test_images:syft_bin-cf22714@sha256:c27b02c6322180fd8a7a3097d2b430bfdf9ea52ecf136edf258458e82f2c6f21
@@ -0,0 +1,6 @@
FROM ibmjava:8@sha256:05ef6b0f754aa3a8cebcec36260a70c234a217b21240a998604f33459037bc08 AS builder
FROM scratch
COPY --from=builder /opt/ibm/java/jre/bin/java /opt/ibm/java/jre/bin/
COPY --from=builder /opt/ibm/java/jre/lib/amd64/jli/libjli.so /opt/ibm/java/jre/lib/amd64/jli/
@@ -0,0 +1,6 @@
FROM ibmjava:8-jre@sha256:3588cd1cc9b8646fe03b3b15210e69b1b520f1321f8518b69c0e7013d702fd23 AS builder
FROM scratch
COPY --from=builder /opt/ibm/java/jre/bin/java /opt/ibm/java/jre/bin/
COPY --from=builder /opt/ibm/java/jre/lib/amd64/jli/libjli.so /opt/ibm/java/jre/lib/amd64/jli/
@@ -0,0 +1,6 @@
FROM ibmjava:8-sdk-alpine@sha256:4f8ad2029e78f7b91721745a77fc6011a7c0e09b9edeffb6b20b6ec34a6e63cd AS builder
FROM scratch
COPY --from=builder /opt/ibm/java/bin/jdb /opt/ibm/java/bin/
COPY --from=builder /opt/ibm/java/lib/amd64/jli/libjli.so /opt/ibm/java/lib/amd64/jli/
@@ -0,0 +1,5 @@
FROM azul/zulu-openjdk:21-jre-headless@sha256:581ebc852fcd9b52a5979b9b90dbe7cca2736bc1ecf1b9bd08ec41212d6675a4 AS builder
FROM scratch
COPY --from=builder /usr/lib/jvm/zulu21-ca-amd64/bin/java /usr/lib/jvm/zulu21-ca-amd64/bin/
@@ -0,0 +1,5 @@
FROM azul/zulu-openjdk:8-latest@sha256:7e3116bf36566e046b763b4a33f410f07f591bc84c391aae4f7891f5ecbb6764 AS builder
FROM scratch
COPY --from=builder /usr/lib/jvm/zulu8-ca-amd64/bin/jdb /usr/lib/jvm/zulu8-ca-amd64/bin/
@@ -31,7 +31,7 @@ func BranchingEvidenceMatcher(classifiers ...Classifier) EvidenceMatcher {
if err != nil {
return nil, err
}
return rdr, nil
return &nonClosingUnionReader{rdr}, nil
},
})
if len(pkgs) > 0 || err != nil {
@@ -41,3 +41,11 @@ func BranchingEvidenceMatcher(classifiers ...Classifier) EvidenceMatcher {
return nil, nil
}
}
type nonClosingUnionReader struct {
unionreader.UnionReader
}
func (c *nonClosingUnionReader) Close() error {
return nil
}
@@ -13148,6 +13148,9 @@
"js_composer": [
"cpe:2.3:a:wpbakery:page_builder:*:*:*:*:*:wordpress:*:*"
],
"jsfiddle-shortcode": [
"cpe:2.3:a:wvega:jsfiddle_shortcode:*:*:*:*:*:wordpress:*:*"
],
"jsmol2wp": [
"cpe:2.3:a:jsmol2wp_project:jsmol2wp:*:*:*:*:*:wordpress:*:*"
],
@@ -13654,9 +13657,41 @@
"lokalyze-call-now": [
"cpe:2.3:a:lokalyze:call_me_now:*:*:*:*:*:wordpress:*:*"
],
"ltl-freight-quotes-abf-freight-edition": [
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:abf:wordpress:*:*",
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:abf_freight:wordpress:*:*"
],
"ltl-freight-quotes-globaltranz-edition": [
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:globaltranz:wordpress:*:*",
"cpe:2.3:a:eniture:small_package_quotes:*:*:*:*:ups:wordpress:*:*"
],
"ltl-freight-quotes-odfl-edition": [
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:odfl:wordpress:*:*"
],
"ltl-freight-quotes-purolator-freight-edition": [
"cpe:2.3:a:eniture:small_package_quotes:*:*:*:*:purolator:wordpress:*:*"
],
"ltl-freight-quotes-saia-edition": [
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:saia:wordpress:*:*"
],
"ltl-freight-quotes-sefl-edition": [
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:sefl:wordpress:*:*",
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:southeastern_freight_lines:wordpress:*:*"
],
"ltl-freight-quotes-unishippers-edition": [
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:unishippers:wordpress:*:*"
],
"ltl-freight-quotes-ups-edition": [
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:tforce:wordpress:*:*",
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:ups:wordpress:*:*"
],
"ltl-freight-quotes-worldwide-express-edition": [
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:worldwide_express:wordpress:*:*",
"cpe:2.3:a:eniture:small_package_quotes:*:*:*:*:worldwide_express:wordpress:*:*"
],
"ltl-freight-quotes-xpo-edition": [
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:xpo:wordpress:*:*"
],
"lucas-string-replace": [
"cpe:2.3:a:lucasstad:lucas_string_replace:*:*:*:*:*:wordpress:*:*"
],
@@ -16612,6 +16647,9 @@
"shiny-buttons": [
"cpe:2.3:a:wpeden:shiny_buttons:*:*:*:*:*:wordpress:*:*"
],
"shipengine-shipping-quotes": [
"cpe:2.3:a:eniture:shipengine_shipping_quotes:*:*:*:*:*:wordpress:*:*"
],
"shopello": [
"cpe:2.3:a:shopello_api_project:shopello_api:*:*:*:*:*:wordpress:*:*"
],
@@ -17177,13 +17215,28 @@
"slideshow-se": [
"cpe:2.3:a:slideshow_se_project:slideshow_se:*:*:*:*:*:wordpress:*:*"
],
"small-package-quotes-fedex-edition": [
"cpe:2.3:a:eniture:small_package_quotes:*:*:*:*:fedex:wordpress:*:*"
],
"small-package-quotes-purolator-edition": [
"cpe:2.3:a:eniture:small_package_quotes:*:*:*:*:purolator:wordpress:*:*"
],
"small-package-quotes-unishippers-edition": [
"cpe:2.3:a:eniture:ltl_freight_quotes:*:*:*:*:unishippers:wordpress:*:*",
"cpe:2.3:a:eniture:small_package_quotes:*:*:*:*:unishippers:wordpress:*:*"
],
"small-package-quotes-ups-edition": [
"cpe:2.3:a:eniture:small_package_quotes:*:*:*:*:ups:wordpress:*:*",
"cpe:2.3:a:eniture:small_package_quotes:*:*:*:*:usps:wordpress:*:*",
"cpe:2.3:a:eniture:small_package_quotes:*:*:*:*:wee:wordpress:*:*",
"cpe:2.3:a:enituretechnology:small_package_quotes:*:*:*:*:ups:wordpress:*:*"
],
"small-package-quotes-usps-edition": [
"cpe:2.3:a:eniture:small_package_quotes:*:*:*:*:usps:wordpress:*:*"
],
"small-package-quotes-wwe-edition": [
"cpe:2.3:a:eniture:small_package_quotes:*:*:*:*:wee:wordpress:*:*"
],
"smart-app-banner": [
"cpe:2.3:a:wandlesoftware:smart_app_banner:*:*:*:*:*:wordpress:*:*"
],
+21 -2
View File
@@ -14,6 +14,7 @@ import (
"github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/internal/log"
"github.com/anchore/syft/internal/spdxlicense"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/license"
@@ -132,6 +133,13 @@ func stripUnwantedCharacters(rawURL string) (string, error) {
}
func NewLicenseFromFieldsWithContext(ctx context.Context, value, url string, location *file.Location) License {
// If value is empty but URL is provided, try to enrich from SPDX database
if value == "" && url != "" {
if info, found := spdxlicense.LicenseByURL(url); found {
value = info.ID
}
}
lics := newLicenseBuilder().WithValues(value).WithURLs(url).WithOptionalLocation(location).Build(ctx).ToSlice()
if len(lics) > 0 {
return lics[0]
@@ -294,11 +302,22 @@ func (b *licenseBuilder) Build(ctx context.Context) LicenseSet {
if set.Empty() && len(b.urls) > 0 {
// if we have no values or contents, but we do have URLs, let's make a license with the URLs
set.Add(License{
// try to enrich the license by looking up the URL in the SPDX database
license := License{
Type: b.tp,
URLs: b.urls,
Locations: locations,
})
}
// attempt to fill in missing license information from the first URL
if len(b.urls) > 0 {
if info, found := spdxlicense.LicenseByURL(b.urls[0]); found {
license.Value = info.ID
license.SPDXExpression = info.ID
}
}
set.Add(license)
}
return set
+180
View File
@@ -0,0 +1,180 @@
package pkg
import (
"context"
"testing"
)
func TestNewLicenseFromFieldsWithContext_URLEnrichment(t *testing.T) {
tests := []struct {
name string
value string
url string
wantValue string
wantHasURL bool
}{
{
name: "Empty value with MIT URL should enrich",
value: "",
url: "http://opensource.org/licenses/MIT",
wantValue: "MIT",
wantHasURL: true,
},
{
name: "Empty value with Apache URL should enrich",
value: "",
url: "https://www.apache.org/licenses/LICENSE-2.0",
wantValue: "Apache-2.0",
wantHasURL: true,
},
{
name: "Non-empty value should not be overridden",
value: "Custom-License",
url: "http://opensource.org/licenses/MIT",
wantValue: "Custom-License",
wantHasURL: true,
},
{
name: "Unknown URL should not enrich",
value: "",
url: "https://example.com/unknown-license",
wantValue: "",
wantHasURL: true,
},
{
name: "Empty value and empty URL",
value: "",
url: "",
wantValue: "",
wantHasURL: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
license := NewLicenseFromFieldsWithContext(ctx, tt.value, tt.url, nil)
if license.Value != tt.wantValue {
t.Errorf("NewLicenseFromFieldsWithContext() Value = %v, want %v", license.Value, tt.wantValue)
}
hasURL := len(license.URLs) > 0
if hasURL != tt.wantHasURL {
t.Errorf("NewLicenseFromFieldsWithContext() has URL = %v, want %v", hasURL, tt.wantHasURL)
}
if tt.wantHasURL && tt.url != "" && license.URLs[0] != tt.url {
t.Errorf("NewLicenseFromFieldsWithContext() URL = %v, want %v", license.URLs[0], tt.url)
}
})
}
}
func TestLicenseBuilder_URLOnlyEnrichment(t *testing.T) {
tests := []struct {
name string
urls []string
wantValue string
wantSPDX string
wantHasURL bool
}{
{
name: "MIT URL only should enrich",
urls: []string{"http://opensource.org/licenses/MIT"},
wantValue: "MIT",
wantSPDX: "MIT",
wantHasURL: true,
},
{
name: "Apache URL only should enrich",
urls: []string{"https://www.apache.org/licenses/LICENSE-2.0"},
wantValue: "Apache-2.0",
wantSPDX: "Apache-2.0",
wantHasURL: true,
},
{
name: "Multiple URLs should use first",
urls: []string{"http://opensource.org/licenses/MIT", "https://example.com/other"},
wantValue: "MIT",
wantSPDX: "MIT",
wantHasURL: true,
},
{
name: "Unknown URL should not enrich",
urls: []string{"https://example.com/unknown-license"},
wantValue: "",
wantSPDX: "",
wantHasURL: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
builder := newLicenseBuilder().WithURLs(tt.urls...)
licenses := builder.Build(ctx).ToSlice()
if len(licenses) == 0 {
t.Fatal("Expected at least one license")
}
license := licenses[0]
if license.Value != tt.wantValue {
t.Errorf("License Value = %v, want %v", license.Value, tt.wantValue)
}
if license.SPDXExpression != tt.wantSPDX {
t.Errorf("License SPDXExpression = %v, want %v", license.SPDXExpression, tt.wantSPDX)
}
hasURL := len(license.URLs) > 0
if hasURL != tt.wantHasURL {
t.Errorf("License has URL = %v, want %v", hasURL, tt.wantHasURL)
}
if tt.wantHasURL && len(tt.urls) > 0 {
if len(license.URLs) != len(tt.urls) {
t.Errorf("License URL count = %v, want %v", len(license.URLs), len(tt.urls))
}
if license.URLs[0] != tt.urls[0] {
t.Errorf("License first URL = %v, want %v", license.URLs[0], tt.urls[0])
}
}
})
}
}
func TestNewLicenseFromURLsWithContext_URLEnrichment(t *testing.T) {
tests := []struct {
name string
value string
urls []string
wantValue string
}{
{
name: "Empty value with MIT URL should enrich via builder",
value: "",
urls: []string{"http://opensource.org/licenses/MIT"},
wantValue: "MIT",
},
{
name: "Non-empty value should not be changed",
value: "Custom-License",
urls: []string{"http://opensource.org/licenses/MIT"},
wantValue: "Custom-License",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
license := NewLicenseFromURLsWithContext(ctx, tt.value, tt.urls...)
if license.Value != tt.wantValue {
t.Errorf("NewLicenseFromURLsWithContext() Value = %v, want %v", license.Value, tt.wantValue)
}
})
}
}
+3 -2
View File
@@ -1,8 +1,9 @@
package source
type Alias struct {
Name string `json:"name" yaml:"name" mapstructure:"name"`
Version string `json:"version" yaml:"version" mapstructure:"version"`
Name string `json:"name" yaml:"name" mapstructure:"name"`
Version string `json:"version" yaml:"version" mapstructure:"version"`
Supplier string `json:"supplier" yaml:"supplier" mapstructure:"supplier"`
}
func (a *Alias) IsEmpty() bool {
+1
View File
@@ -5,5 +5,6 @@ type Description struct {
ID string `hash:"ignore"` // the id generated from the parent source struct
Name string `hash:"ignore"`
Version string `hash:"ignore"`
Supplier string `hash:"ignore"`
Metadata interface{}
}
@@ -112,6 +112,7 @@ func (s directorySource) ID() artifact.ID {
func (s directorySource) Describe() source.Description {
name := cleanDirPath(s.config.Path, s.config.Base)
version := ""
supplier := ""
if !s.config.Alias.IsEmpty() {
a := s.config.Alias
if a.Name != "" {
@@ -120,11 +121,15 @@ func (s directorySource) Describe() source.Description {
if a.Version != "" {
version = a.Version
}
if a.Supplier != "" {
supplier = a.Supplier
}
}
return source.Description{
ID: string(s.id),
Name: name,
Version: version,
ID: string(s.id),
Name: name,
Version: version,
Supplier: supplier,
Metadata: source.DirectoryMetadata{
Path: s.config.Path,
Base: s.config.Base,
@@ -475,12 +475,13 @@ func Test_DirectorySource_ID(t *testing.T) {
wantErr: require.Error,
},
{
name: "to dir with name and version",
name: "to dir with name and version and supplier",
cfg: Config{
Path: "./test-fixtures",
Alias: source.Alias{
Name: "name-me-that!",
Version: "version-me-this!",
Name: "name-me-that!",
Version: "version-me-this!",
Supplier: "some-supplier",
},
},
want: artifact.ID("51a5f2a1536cf4b5220d4247814b07eec5862ab0547050f90e9ae216548ded7e"),
@@ -490,8 +491,9 @@ func Test_DirectorySource_ID(t *testing.T) {
cfg: Config{
Path: "./test-fixtures/image-simple",
Alias: source.Alias{
Name: "name-me-that!",
Version: "version-me-this!",
Name: "name-me-that!",
Version: "version-me-this!",
Supplier: "some-supplier",
},
},
// note: this must match the previous value because the alias should trump the path info
+9 -3
View File
@@ -123,6 +123,7 @@ func (s fileSource) ID() artifact.ID {
func (s fileSource) Describe() source.Description {
name := path.Base(s.config.Path)
version := s.digestForVersion
supplier := ""
if !s.config.Alias.IsEmpty() {
a := s.config.Alias
if a.Name != "" {
@@ -132,11 +133,16 @@ func (s fileSource) Describe() source.Description {
if a.Version != "" {
version = a.Version
}
if a.Supplier != "" {
supplier = a.Supplier
}
}
return source.Description{
ID: string(s.id),
Name: name,
Version: version,
ID: string(s.id),
Name: name,
Version: version,
Supplier: supplier,
Metadata: source.FileMetadata{
Path: s.config.Path,
Digests: s.digests,
@@ -51,6 +51,7 @@ func (s stereoscopeImageSource) Describe() source.Description {
a := s.config.Alias
name := a.Name
supplier := a.Supplier
nameIfUnset := func(n string) {
if name != "" {
return
@@ -90,6 +91,7 @@ func (s stereoscopeImageSource) Describe() source.Description {
ID: string(s.id),
Name: name,
Version: version,
Supplier: supplier,
Metadata: s.metadata,
}
}
+15
View File
@@ -71,6 +71,21 @@ func TestPackagesCmdFlags(t *testing.T) {
assertSuccessfulReturnCode,
},
},
{
name: "source flags override bom metadata",
args: []string{
"scan",
"--source-name", "custom-name",
"--source-version", "custom-version",
"--source-supplier", "custom-supplier",
"-o", "json", coverageImage},
assertions: []traitAssertion{
assertInOutput("custom-name"),
assertInOutput("custom-version"),
assertInOutput("custom-supplier"),
assertSuccessfulReturnCode,
},
},
// I haven't been able to reproduce locally yet, but in CI this has proven to be unstable:
// For the same commit:
// pass: https://github.com/anchore/syft/runs/4611344142?check_suite_focus=true