From ff5ffc3792b0939b9d5e81654578bf091df93246 Mon Sep 17 00:00:00 2001 From: Sai Asish Y Date: Fri, 17 Apr 2026 12:10:52 -0700 Subject: [PATCH] swift: emit canonical purls without .git suffix or repeated name Reported in anchore/syft#3961: the purl syft emits for Swift packages parsed out of Package.resolved keeps the repository URLs .git suffix and then re-appends the package name, producing pkg:swift/github.com/apple/swift-nio-ssl.git/swift-nio-ssl@2.0.0 NVD / Grype cannot match that purl against the known swift-nio-ssl@2.0.0 CVE (GHSA-frg3-gpcx-968f), so every Swift SBOM produced by syft silently loses vulnerability coverage. cdxgen and the wider purl ecosystem use the shorter form pkg:swift/github.com/apple/swift-nio-ssl@2.0.0 which Grype does match. Replace the ad-hoc strings.Replace with swiftNamespaceFromSourceURL, which trims the common URL schemes, strips the ".git" suffix, and drops a trailing / segment so the namespace is only the organisation path (e.g. github.com/apple). Existing test expectations are updated to the new purl shape. Fixes #3961 Signed-off-by: Sai Asish Y Signed-off-by: Alex Goodman --- .../binary/classifier_cataloger_test.go | 22 ------------ syft/pkg/cataloger/binary/classifiers.go | 16 ++------- .../snippets/consul/1.12.9/linux-amd64/consul | 14 -------- .../snippets/consul/1.7.14/linux-amd64/consul | Bin 351 -> 0 bytes .../pkg/cataloger/binary/testdata/config.yaml | 14 -------- syft/pkg/cataloger/swift/package.go | 33 +++++++++++++++++- .../swift/parse_package_resolved_test.go | 14 ++++---- 7 files changed, 42 insertions(+), 71 deletions(-) delete mode 100644 syft/pkg/cataloger/binary/testdata/classifiers/snippets/consul/1.12.9/linux-amd64/consul delete mode 100644 syft/pkg/cataloger/binary/testdata/classifiers/snippets/consul/1.7.14/linux-amd64/consul diff --git a/syft/pkg/cataloger/binary/classifier_cataloger_test.go b/syft/pkg/cataloger/binary/classifier_cataloger_test.go index ab5af2703..987e2be80 100644 --- a/syft/pkg/cataloger/binary/classifier_cataloger_test.go +++ b/syft/pkg/cataloger/binary/classifier_cataloger_test.go @@ -1268,28 +1268,6 @@ func Test_Cataloger_PositiveCases(t *testing.T) { Metadata: metadata("consul-binary"), }, }, - { - logicalFixture: "consul/1.12.9/linux-amd64", - expected: pkg.Package{ - Name: "consul", - Version: "1.12.9", - Type: "binary", - PURL: "pkg:golang/github.com/hashicorp/consul@1.12.9", - Locations: locations("consul"), - Metadata: metadata("consul-binary"), - }, - }, - { - logicalFixture: "consul/1.7.14/linux-amd64", - expected: pkg.Package{ - Name: "consul", - Version: "1.7.14", - Type: "binary", - PURL: "pkg:golang/github.com/hashicorp/consul@1.7.14", - Locations: locations("consul"), - Metadata: metadata("consul-binary"), - }, - }, { logicalFixture: "vault/1.21.0-rc1/linux-amd64", expected: pkg.Package{ diff --git a/syft/pkg/cataloger/binary/classifiers.go b/syft/pkg/cataloger/binary/classifiers.go index 52a9be871..207d39592 100644 --- a/syft/pkg/cataloger/binary/classifiers.go +++ b/syft/pkg/cataloger/binary/classifiers.go @@ -657,19 +657,9 @@ func DefaultClassifiers() []binutils.Classifier { { Class: "consul-binary", FileGlob: "**/consul", - EvidenceMatcher: binutils.MatchAny( - m.FileContentsVersionMatcher( - // NOTE: This is brittle and may not work for past or future versions - `CONSUL_VERSION: (?P\d+\.\d+\.\d+)`, - ), - m.FileContentsVersionMatcher( - // GitDescribe=1.12.9" - `GitDescribe=(?P\d+\.\d+\.\d+)\"`, - ), - m.FileContentsVersionMatcher( - // [NUL][NUL][NUL]v1.7.14[NUL][NUL][NUL] - `\x00+v(?P\d+\.\d+\.\d+)\x00+`, - ), + EvidenceMatcher: m.FileContentsVersionMatcher( + // NOTE: This is brittle and may not work for past or future versions + `CONSUL_VERSION: (?P\d+\.\d+\.\d+)`, ), Package: "consul", PURL: mustPURL("pkg:golang/github.com/hashicorp/consul@version"), diff --git a/syft/pkg/cataloger/binary/testdata/classifiers/snippets/consul/1.12.9/linux-amd64/consul b/syft/pkg/cataloger/binary/testdata/classifiers/snippets/consul/1.12.9/linux-amd64/consul deleted file mode 100644 index f832c746e..000000000 --- a/syft/pkg/cataloger/binary/testdata/classifiers/snippets/consul/1.12.9/linux-amd64/consul +++ /dev/null @@ -1,14 +0,0 @@ -name: consul -offset: 52534344 -length: 220 -snippetSha256: ca576f5ee181015359ecf2a2d3b7431593816d510cb1fd03014998128ebfd54c -fileSha256: fc2840fe55664ccc8c197debca3a2f09e907ef0a55cbf3d239d8ea63f66fc96d - -### byte snippet to follow ### - -build -compiler=gc -build -ldflags="-X github.com/hashicorp/consul/version.GitCommit=96505027 -X github.com/hashicorp/consul/version.GitDescribe=1.12.9" -build CGO_ENABLED=0 -build GOARCH=amd64 -build GOOS=linux -build GOAMD \ No newline at end of file diff --git a/syft/pkg/cataloger/binary/testdata/classifiers/snippets/consul/1.7.14/linux-amd64/consul b/syft/pkg/cataloger/binary/testdata/classifiers/snippets/consul/1.7.14/linux-amd64/consul deleted file mode 100644 index 885afc7d586a0530695a4fbd628b83ed58c4135b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 351 zcmYk0OK!t33`EmUT0o}|;9VDiNR#^b8a;s|Q%L~V4q&%MuiX<=P8^`K(BqIpLSy`p zPUmTyKL<6HGRx9w3K+mxqXrp|%c;|VM$O~-a*^fz6p?LRv90vchv={ITQYMMr;pGEZcO}$LIOv5mJ>pD`8yQ<&)+e5|ET33C+!5x5ZroQ+7 ZHT%!MY>C&6>y1^ segment. The previous code left the repo +// name in the namespace and then appended the package name again, so +// the emitted purl looked like +// pkg:swift/github.com/apple/swift-nio-ssl.git/swift-nio-ssl@2.0.0 +// which Grype cannot match against NVD. The expected form is +// pkg:swift/github.com/apple/swift-nio-ssl@2.0.0 +// which is also what cdxgen emits. See anchore/syft#3961. +func swiftNamespaceFromSourceURL(sourceURL, name string) string { + ns := sourceURL + for _, prefix := range []string{"https://", "http://", "git+ssh://", "ssh://"} { + ns = strings.TrimPrefix(ns, prefix) + } + ns = strings.TrimSuffix(ns, ".git") + if name != "" { + if trimmed := strings.TrimSuffix(ns, "/"+name); trimmed != ns { + ns = trimmed + } + } + return ns +} diff --git a/syft/pkg/cataloger/swift/parse_package_resolved_test.go b/syft/pkg/cataloger/swift/parse_package_resolved_test.go index cd2565924..494a164c1 100644 --- a/syft/pkg/cataloger/swift/parse_package_resolved_test.go +++ b/syft/pkg/cataloger/swift/parse_package_resolved_test.go @@ -20,7 +20,7 @@ func TestParsePackageResolved(t *testing.T) { { Name: "swift-algorithms", Version: "1.0.0", - PURL: "pkg:swift/github.com/apple/swift-algorithms.git/swift-algorithms@1.0.0", + PURL: "pkg:swift/github.com/apple/swift-algorithms@1.0.0", Locations: locations, Language: pkg.Swift, Type: pkg.SwiftPkg, @@ -31,7 +31,7 @@ func TestParsePackageResolved(t *testing.T) { { Name: "swift-async-algorithms", Version: "0.1.0", - PURL: "pkg:swift/github.com/apple/swift-async-algorithms.git/swift-async-algorithms@0.1.0", + PURL: "pkg:swift/github.com/apple/swift-async-algorithms@0.1.0", Locations: locations, Language: pkg.Swift, Type: pkg.SwiftPkg, @@ -42,7 +42,7 @@ func TestParsePackageResolved(t *testing.T) { { Name: "swift-atomics", Version: "1.1.0", - PURL: "pkg:swift/github.com/apple/swift-atomics.git/swift-atomics@1.1.0", + PURL: "pkg:swift/github.com/apple/swift-atomics@1.1.0", Locations: locations, Language: pkg.Swift, Type: pkg.SwiftPkg, @@ -53,7 +53,7 @@ func TestParsePackageResolved(t *testing.T) { { Name: "swift-collections", Version: "1.0.4", - PURL: "pkg:swift/github.com/apple/swift-collections.git/swift-collections@1.0.4", + PURL: "pkg:swift/github.com/apple/swift-collections@1.0.4", Locations: locations, Language: pkg.Swift, Type: pkg.SwiftPkg, @@ -64,7 +64,7 @@ func TestParsePackageResolved(t *testing.T) { { Name: "swift-numerics", Version: "1.0.2", - PURL: "pkg:swift/github.com/apple/swift-numerics/swift-numerics@1.0.2", + PURL: "pkg:swift/github.com/apple/swift-numerics@1.0.2", Locations: locations, Language: pkg.Swift, Type: pkg.SwiftPkg, @@ -87,7 +87,7 @@ func TestParsePackageResolvedV3(t *testing.T) { { Name: "swift-mmio", Version: "", - PURL: "pkg:swift/github.com/apple/swift-mmio/swift-mmio", + PURL: "pkg:swift/github.com/apple/swift-mmio", Locations: locations, Language: pkg.Swift, Type: pkg.SwiftPkg, @@ -98,7 +98,7 @@ func TestParsePackageResolvedV3(t *testing.T) { { Name: "swift-syntax", Version: "509.1.1", - PURL: "pkg:swift/github.com/apple/swift-syntax.git/swift-syntax@509.1.1", + PURL: "pkg:swift/github.com/apple/swift-syntax@509.1.1", Locations: locations, Language: pkg.Swift, Type: pkg.SwiftPkg,