From c101af94396a2c946912c176f209a2f2e6be9eac Mon Sep 17 00:00:00 2001 From: Ahmed ElMallah Date: Mon, 31 Aug 2026 01:10:27 -0700 Subject: [PATCH 1/2] feat: adopt bomly-sdk v0.7.0 and resolve repositories from origins The mechanical half matches the other plugin migrations: sdk.Dependency becomes sdk.DependencyNode, fixtures route through the testkit constructors because identity is minted there and the fields holding it are unexported. The half that is not mechanical, and the reason this repo was flagged: this matcher resolved a package's GitHub repository by reading the PURL's repository_url / vcs_url / download_url qualifiers. ADR-0041 relocates those out of a package PURL and into origins when a node is constructed, so that step now finds nothing for any package that came through the graph. The failure would have been silent. Resolution returns "", the package is never scored, and the result is indistinguishable from a package that has no GitHub source -- the tests stayed green through the bump, which is exactly why the plan called this bump behavioural. resolveRepo now reads Package.DetectedOrigins first, which is the vetted ADR-0033 evidence phase 1.4 added for this. A repository claim outranks an artifact URL: the first names a source repository, the second is a download location that happens to be hosted on GitHub. The qualifier step is kept after it, since a PURL from an ingested SBOM never went through a node constructor and can still carry them. Delegation: dropped the github.com/anchore/packageurl-go import for purlkit.Parse. The fork was the deprecated path the SDK removed at v0.6.0; purlkit is the kit over the official packageurl-go. Two mutations verified: removing the origins step reproduces the silent regression, and preferring an artifact URL over a repository claim resolves a mirror instead of the source. --- go.mod | 9 ++-- go.sum | 28 ++++++++--- plugin/matcher_test.go | 25 +++++----- plugin/module_test.go | 7 +-- plugin/reporesolve.go | 47 +++++++++++++++--- plugin/reporesolve_origins_test.go | 79 ++++++++++++++++++++++++++++++ 6 files changed, 163 insertions(+), 32 deletions(-) create mode 100644 plugin/reporesolve_origins_test.go diff --git a/go.mod b/go.mod index 1a99fa7..222a9f8 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,16 @@ module github.com/bomly-dev/bomly-plugin-scorecard-matcher -go 1.26.3 +go 1.27.0 require ( - github.com/anchore/packageurl-go v0.2.0 - github.com/bomly-dev/bomly-sdk v0.3.0 + github.com/bomly-dev/bomly-sdk v0.7.0 go.uber.org/zap v1.28.0 ) require ( + github.com/CycloneDX/cyclonedx-go v0.11.0 // indirect github.com/fatih/color v1.13.0 // indirect + github.com/github/go-spdx/v2 v2.7.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-plugin v1.8.0 // indirect @@ -17,6 +18,8 @@ require ( github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/oklog/run v1.1.0 // indirect + github.com/package-url/packageurl-go v0.1.7 // indirect + github.com/spdx/tools-golang v0.6.0-rc4 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/net v0.57.0 // indirect golang.org/x/sys v0.47.0 // indirect diff --git a/go.sum b/go.sum index 2fe7f47..37c782c 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,9 @@ -github.com/anchore/packageurl-go v0.2.0 h1:CkrM4RMUwrEGAiE1OVlxaZNzWj0TuHRey7o4T/EAErk= -github.com/anchore/packageurl-go v0.2.0/go.mod h1:2JCgOQMIsqZ7TmliXG4PnUthPJAKE3mWQbsW2XHjAOE= -github.com/bomly-dev/bomly-sdk v0.3.0 h1:JtC7qZ9yq3r4fUYyq7e/Os4f9wGMa4Qot8U0l9MepFA= -github.com/bomly-dev/bomly-sdk v0.3.0/go.mod h1:yn1LBkoHG9gDBXKyRj0UNJo0BlXl8Bj9Ymb3WKLIh78= +github.com/CycloneDX/cyclonedx-go v0.11.0 h1:GokP8FiRC+foiuwWhSSLpSD5H4hSWtGnR3wo7apkBFI= +github.com/CycloneDX/cyclonedx-go v0.11.0/go.mod h1:vUvbCXQsEm48OI6oOlanxstwNByXjCZ2wuleUlwGEO8= +github.com/bomly-dev/bomly-sdk v0.7.0 h1:LIZ8AEXU/SASnoTZSa7ThKFtczvR/h6axwtpYEpaLQs= +github.com/bomly-dev/bomly-sdk v0.7.0/go.mod h1:7RJLUANK8xHMZ5/r45zYxGyKUHC8yQiVem22yM0MyZw= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -11,6 +13,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/github/go-spdx/v2 v2.7.0 h1:GzfXx4wFdlilARxmFRXW/mgUy3A4vSqZocCMFV6XFdQ= +github.com/github/go-spdx/v2 v2.7.0/go.mod h1:Ftc45YYG1WzpzwEPKRVm9Jv8vDqOrN4gWoCkK+bHer0= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -38,12 +42,24 @@ github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPn github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/package-url/packageurl-go v0.1.7 h1:iFWg6tzAjLA6F/qX3M5nZaiMHJgc+p2zxVyr/fY+sZY= +github.com/package-url/packageurl-go v0.1.7/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spdx/tools-golang v0.6.0-rc4 h1:2GkvNr0DcnJHY9BDm3OYHo229jZS/h4qYDK+tHYXPOo= +github.com/spdx/tools-golang v0.6.0-rc4/go.mod h1:ruCHu3shgy7bVbZ7gtEU4Gq4fI08n2SdXtgV5PoN/OM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/terminalstatic/go-xsd-validate v0.1.6 h1:TenYeQ3eY631qNi1/cTmLH/s2slHPRKTTHT+XSHkepo= +github.com/terminalstatic/go-xsd-validate v0.1.6/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= diff --git a/plugin/matcher_test.go b/plugin/matcher_test.go index cb4a738..b33418d 100644 --- a/plugin/matcher_test.go +++ b/plugin/matcher_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/bomly-dev/bomly-sdk" + "github.com/bomly-dev/bomly-sdk/testkit" ) const sampleResponse = `{ @@ -45,7 +46,7 @@ func newMatcher(t *testing.T, base string) *Matcher { return m } -func newGraph(t *testing.T, deps ...*sdk.Dependency) *sdk.Graph { +func newGraph(t *testing.T, deps ...*sdk.DependencyNode) *sdk.Graph { t.Helper() g := sdk.New() for _, d := range deps { @@ -57,8 +58,8 @@ func newGraph(t *testing.T, deps ...*sdk.Dependency) *sdk.Graph { } // scorecardOf returns the enriched scorecard for a dependency from the registry. -func scorecardOf(reg *sdk.PackageRegistry, dep *sdk.Dependency) *sdk.PackageScorecard { - pkg, ok := reg.Get(sdk.CanonicalPackageURLFromDependency(dep)) +func scorecardOf(reg *sdk.PackageRegistry, dep *sdk.DependencyNode) *sdk.PackageScorecard { + pkg, ok := reg.Get(dep.NodeID()) if !ok || pkg == nil { return nil } @@ -76,7 +77,7 @@ func TestMatch_AttachesScorecardToPackages(t *testing.T) { }) matcher := newMatcher(t, base) - dep := sdk.NewDependency(sdk.Dependency{Coordinates: sdk.Coordinates{Name: "scorecard", Version: "v5.0.0", PURL: "pkg:github/ossf/scorecard@v5.0.0"}}) + dep := testkit.MustDependencyNode(t, "pkg:github/ossf/scorecard@v5.0.0") g := newGraph(t, dep) registry := sdk.NewPackageRegistry() @@ -116,7 +117,7 @@ func TestMatch_CacheHitSkipsAPI(t *testing.T) { if err != nil { t.Fatalf("New: %v", err) } - dep1 := sdk.NewDependency(sdk.Dependency{Coordinates: sdk.Coordinates{PURL: "pkg:github/ossf/scorecard@v5.0.0", Version: "v5.0.0"}}) + dep1 := testkit.MustDependencyNode(t, "pkg:github/ossf/scorecard@v5.0.0") reg1 := sdk.NewPackageRegistry() if _, err := matcher1.Match(context.Background(), sdk.MatchRequest{Graph: newGraph(t, dep1), Registry: reg1}); err != nil { t.Fatalf("first Match: %v", err) @@ -127,7 +128,7 @@ func TestMatch_CacheHitSkipsAPI(t *testing.T) { if err != nil { t.Fatalf("New2: %v", err) } - dep2 := sdk.NewDependency(sdk.Dependency{Coordinates: sdk.Coordinates{PURL: "pkg:github/ossf/scorecard@v5.0.0", Version: "v5.0.0"}}) + dep2 := testkit.MustDependencyNode(t, "pkg:github/ossf/scorecard@v5.0.0") reg2 := sdk.NewPackageRegistry() if _, err := matcher2.Match(context.Background(), sdk.MatchRequest{Graph: newGraph(t, dep2), Registry: reg2}); err != nil { t.Fatalf("second Match: %v", err) @@ -146,7 +147,7 @@ func TestMatch_NotFoundCachedAsSentinel(t *testing.T) { }) dir := t.TempDir() - dep1 := sdk.NewDependency(sdk.Dependency{Coordinates: sdk.Coordinates{PURL: "pkg:github/unscored/repo@1.0.0", Version: "1.0.0"}}) + dep1 := testkit.MustDependencyNode(t, "pkg:github/unscored/repo@1.0.0") reg1 := sdk.NewPackageRegistry() matcher, err := New(Config{APIBase: base, CacheDir: dir}) if err != nil { @@ -160,7 +161,7 @@ func TestMatch_NotFoundCachedAsSentinel(t *testing.T) { } // Second invocation should hit the sentinel cache (no extra API call). - dep2 := sdk.NewDependency(sdk.Dependency{Coordinates: sdk.Coordinates{PURL: "pkg:github/unscored/repo@1.0.0", Version: "1.0.0"}}) + dep2 := testkit.MustDependencyNode(t, "pkg:github/unscored/repo@1.0.0") reg2 := sdk.NewPackageRegistry() if _, err := matcher.Match(context.Background(), sdk.MatchRequest{Graph: newGraph(t, dep2), Registry: reg2}); err != nil { t.Fatalf("second Match: %v", err) @@ -176,7 +177,7 @@ func TestMatch_ServerErrorIsNonFatal(t *testing.T) { }) matcher := newMatcher(t, base) - dep := sdk.NewDependency(sdk.Dependency{Coordinates: sdk.Coordinates{PURL: "pkg:github/example/repo@1.0.0", Version: "1.0.0"}}) + dep := testkit.MustDependencyNode(t, "pkg:github/example/repo@1.0.0") reg := sdk.NewPackageRegistry() if _, err := matcher.Match(context.Background(), sdk.MatchRequest{Graph: newGraph(t, dep), Registry: reg}); err != nil { t.Fatalf("Match must not return an error on transport failure; got %v", err) @@ -194,7 +195,7 @@ func TestMatch_SkipsPackagesWithoutResolvableRepo(t *testing.T) { t.Cleanup(srv.Close) matcher := newMatcher(t, srv.URL) - dep := sdk.NewDependency(sdk.Dependency{Coordinates: sdk.Coordinates{PURL: "pkg:npm/internal-only@1.0.0", Version: "1.0.0"}}) + dep := testkit.MustDependencyNode(t, "pkg:npm/internal-only@1.0.0") reg := sdk.NewPackageRegistry() if _, err := matcher.Match(context.Background(), sdk.MatchRequest{Graph: newGraph(t, dep), Registry: reg}); err != nil { t.Fatalf("Match: %v", err) @@ -214,8 +215,8 @@ func TestMatch_ComponentModeOnlyEnrichesTarget(t *testing.T) { }) matcher := newMatcher(t, base) - target := sdk.NewDependency(sdk.Dependency{Coordinates: sdk.Coordinates{PURL: "pkg:github/ossf/scorecard@v5.0.0", Version: "v5.0.0"}}) - other := sdk.NewDependency(sdk.Dependency{Coordinates: sdk.Coordinates{PURL: "pkg:golang/github.com/sirupsen/logrus@v1.9.0", Version: "v1.9.0"}}) + target := testkit.MustDependencyNode(t, "pkg:github/ossf/scorecard@v5.0.0") + other := testkit.MustDependencyNode(t, "pkg:golang/github.com/sirupsen/logrus@v1.9.0") g := newGraph(t, target, other) reg := sdk.NewPackageRegistry() diff --git a/plugin/module_test.go b/plugin/module_test.go index 2f7647f..4216357 100644 --- a/plugin/module_test.go +++ b/plugin/module_test.go @@ -14,6 +14,7 @@ import ( sdk "github.com/bomly-dev/bomly-sdk" "github.com/bomly-dev/bomly-sdk/conformance" + "github.com/bomly-dev/bomly-sdk/testkit" "go.uber.org/zap" ) @@ -74,10 +75,10 @@ func newScorecardFixtureServer(t *testing.T) *httptest.Server { func newDeltaGraphAndRegistry(t *testing.T) (*sdk.Graph, *sdk.PackageRegistry) { t.Helper() - scored := sdk.NewDependency(sdk.Dependency{Coordinates: sdk.Coordinates{Name: "scorecard", Version: "v5.0.0", PURL: "pkg:github/ossf/scorecard@v5.0.0"}}) - unscored := sdk.NewDependency(sdk.Dependency{Coordinates: sdk.Coordinates{Name: "left-pad", Version: "1.3.0", PURL: "pkg:npm/left-pad@1.3.0", Ecosystem: sdk.EcosystemNPM}}) + scored := testkit.MustDependencyNode(t, "pkg:github/ossf/scorecard@v5.0.0") + unscored := testkit.MustDependencyNode(t, "pkg:npm/left-pad@1.3.0") graph := sdk.New() - for _, dep := range []*sdk.Dependency{scored, unscored} { + for _, dep := range []*sdk.DependencyNode{scored, unscored} { if err := graph.AddNode(dep); err != nil { t.Fatalf("AddNode: %v", err) } diff --git a/plugin/reporesolve.go b/plugin/reporesolve.go index 0334b67..e8203d2 100644 --- a/plugin/reporesolve.go +++ b/plugin/reporesolve.go @@ -6,8 +6,8 @@ import ( "regexp" "strings" - "github.com/anchore/packageurl-go" "github.com/bomly-dev/bomly-sdk" + "github.com/bomly-dev/bomly-sdk/purlkit" ) // githubRepoPattern matches an org/repo segment in any github.com URL form @@ -22,12 +22,22 @@ var githubRepoPattern = regexp.MustCompile(`github\.com[/:]([A-Za-z0-9_.-]+)/([A // owner/repo, so it is safe to append to the api.scorecard.dev URL. // // Resolution order, cheapest first: -// 1. PURL `repository_url` / `vcs_url` qualifier (set by Syft for some -// ecosystems). -// 2. PURL of type `golang` — module path is the repo for github.com modules. -// 3. PURL of type `github` — `pkg:github/{owner}/{repo}`. -// 4. PackageResolvedURL — common for npm/pnpm/yarn tarballs hosted on GitHub. -// 5. NPM metadata `repository` link. +// 1. The package's detected origins — the vetted ADR-0033 repository or +// artifact URL the detector recorded. +// 2. PURL `repository_url` / `vcs_url` qualifier, for a PURL that arrived +// from outside the graph and still carries them. +// 3. PURL of type `golang` — module path is the repo for github.com modules. +// 4. PURL of type `github` — `pkg:github/{owner}/{repo}`. +// 5. PackageResolvedURL — common for npm/pnpm/yarn tarballs hosted on GitHub. +// 6. NPM metadata `repository` link. +// +// Origins lead because ADR-0041 moved the evidence there. The URL-valued +// qualifiers this used to read first — repository_url, download_url, vcs_url — +// are relocated out of a package PURL and into origins when a node is +// constructed, so the qualifier step alone would now find nothing for any +// package that came through the graph. That failure is silent: resolution +// returns "" and the package is simply never scored, which looks like a +// package with no GitHub source rather than a bug. // // Multiple packages frequently resolve to the same repo (a monorepo's npm // packages all point at one source); the matcher dedupes by the returned @@ -37,6 +47,9 @@ func resolveRepo(pkg *sdk.Package) string { return "" } + if repo := repoFromOrigins(pkg.DetectedOrigins); repo != "" { + return repo + } if repo := repoFromPURL(pkg.PURL); repo != "" { return repo } @@ -49,12 +62,30 @@ func resolveRepo(pkg *sdk.Package) string { return "" } +// repoFromOrigins reads the vetted origin evidence. A repository claim is +// preferred over an artifact URL: the first names a source repository +// outright, while the second is a download location that merely happens to +// be hosted on GitHub. +func repoFromOrigins(origins []sdk.DependencyOrigin) string { + for _, origin := range origins { + if repo := extractGithubRepo(origin.Repository); repo != "" { + return repo + } + } + for _, origin := range origins { + if repo := extractGithubRepo(origin.ArtifactURL); repo != "" { + return repo + } + } + return "" +} + func repoFromPURL(raw string) string { raw = strings.TrimSpace(raw) if raw == "" { return "" } - parsed, err := packageurl.FromString(raw) + parsed, err := purlkit.Parse(raw) if err != nil { return "" } diff --git a/plugin/reporesolve_origins_test.go b/plugin/reporesolve_origins_test.go new file mode 100644 index 0000000..1626052 --- /dev/null +++ b/plugin/reporesolve_origins_test.go @@ -0,0 +1,79 @@ +package plugin + +import ( + "testing" + + sdk "github.com/bomly-dev/bomly-sdk" +) + +// TestResolveRepoReadsDetectedOrigins pins the fix for a silent regression the +// SDK v0.7.0 bump would otherwise have introduced. +// +// This matcher used to read the repository out of the package PURL's +// repository_url / vcs_url / download_url qualifiers. ADR-0041 relocates those +// out of a package PURL and into origins at node construction, so that path +// now finds nothing for any package that came through the graph — and the +// failure is silent. Resolution returns "", the package is never scored, and +// the result is indistinguishable from a package that genuinely has no GitHub +// source. +func TestResolveRepoReadsDetectedOrigins(t *testing.T) { + cases := []struct { + name string + pkg *sdk.Package + want string + }{ + { + name: "repository origin", + pkg: &sdk.Package{ + PURL: "pkg:npm/left-pad@1.3.0", + DetectedOrigins: []sdk.DependencyOrigin{{Repository: "https://github.com/ossf/scorecard", Revision: "abc123"}}, + }, + want: "github.com/ossf/scorecard", + }, + { + name: "artifact origin hosted on github", + pkg: &sdk.Package{ + PURL: "pkg:npm/left-pad@1.3.0", + DetectedOrigins: []sdk.DependencyOrigin{{ArtifactURL: "https://github.com/ossf/scorecard/archive/v5.0.0.tar.gz"}}, + }, + want: "github.com/ossf/scorecard", + }, + { + name: "a repository claim outranks an artifact URL", + pkg: &sdk.Package{ + PURL: "pkg:npm/left-pad@1.3.0", + DetectedOrigins: []sdk.DependencyOrigin{ + {ArtifactURL: "https://github.com/mirror/copy/archive/v1.tar.gz"}, + {Repository: "https://github.com/ossf/scorecard"}, + }, + }, + want: "github.com/ossf/scorecard", + }, + { + name: "no github source anywhere", + pkg: &sdk.Package{ + PURL: "pkg:npm/left-pad@1.3.0", + DetectedOrigins: []sdk.DependencyOrigin{{Repository: "https://gitlab.com/owner/repo"}}, + }, + want: "", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := resolveRepo(tc.pkg); got != tc.want { + t.Errorf("resolveRepo = %q, want %q", got, tc.want) + } + }) + } +} + +// TestResolveRepoStillReadsQualifiers pins that the qualifier path is kept as +// a fallback. A PURL that arrived from outside the graph — an ingested SBOM +// that was never through a node constructor — can still carry them, and +// dropping the step would lose a source the document did state. +func TestResolveRepoStillReadsQualifiers(t *testing.T) { + pkg := &sdk.Package{PURL: "pkg:npm/left-pad@1.3.0?repository_url=https://github.com/ossf/scorecard"} + if got := resolveRepo(pkg); got != "github.com/ossf/scorecard" { + t.Errorf("resolveRepo = %q, want the qualifier fallback to still work", got) + } +} From 6f9a62f2de07bc2cfd7c0fc98283977d1d6ad2e3 Mon Sep 17 00:00:00 2001 From: Ahmed ElMallah Date: Mon, 31 Aug 2026 22:20:53 -0700 Subject: [PATCH 2/2] chore: pin bomly-sdk v0.8.0 The migration was developed against the release candidate through a go.work overlay; this pins the published tag. v0.8.0 rather than v0.7.1 because the SDK fixes added exported API, which is a minor bump under semver -- gorelease named the version. Full suite green under -race against the real tag, with no overlay. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 222a9f8..7e7030e 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/bomly-dev/bomly-plugin-scorecard-matcher go 1.27.0 require ( - github.com/bomly-dev/bomly-sdk v0.7.0 + github.com/bomly-dev/bomly-sdk v0.8.0 go.uber.org/zap v1.28.0 ) diff --git a/go.sum b/go.sum index 37c782c..adcd000 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/CycloneDX/cyclonedx-go v0.11.0 h1:GokP8FiRC+foiuwWhSSLpSD5H4hSWtGnR3wo7apkBFI= github.com/CycloneDX/cyclonedx-go v0.11.0/go.mod h1:vUvbCXQsEm48OI6oOlanxstwNByXjCZ2wuleUlwGEO8= -github.com/bomly-dev/bomly-sdk v0.7.0 h1:LIZ8AEXU/SASnoTZSa7ThKFtczvR/h6axwtpYEpaLQs= -github.com/bomly-dev/bomly-sdk v0.7.0/go.mod h1:7RJLUANK8xHMZ5/r45zYxGyKUHC8yQiVem22yM0MyZw= +github.com/bomly-dev/bomly-sdk v0.8.0 h1:XpIr0yS5vg2B2oMt9xPornZ+krgy2AYlm/ts/At4JA0= +github.com/bomly-dev/bomly-sdk v0.8.0/go.mod h1:7RJLUANK8xHMZ5/r45zYxGyKUHC8yQiVem22yM0MyZw= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=