fix(rust): omit Cargo PURLs for local packages (#5105)

Signed-off-by: Enes Deniz <142517728+3nesdeniz@users.noreply.github.com>
This commit is contained in:
Enes Deniz 2026-07-27 17:50:13 +03:00 committed by GitHub
parent 2dcf5163b8
commit 86baeeb481
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 139 additions and 2 deletions

View File

@ -14,7 +14,7 @@ func newPackageFromCargoMetadata(m pkg.RustCargoLockEntry, locations ...file.Loc
Name: m.Name, Name: m.Name,
Version: m.Version, Version: m.Version,
Locations: file.NewLocationSet(locations...), Locations: file.NewLocationSet(locations...),
PURL: packageURL(m.Name, m.Version), PURL: cargoLockPackageURL(m),
Language: pkg.Rust, Language: pkg.Rust,
Type: pkg.RustPkg, Type: pkg.RustPkg,
Metadata: m, Metadata: m,
@ -25,6 +25,18 @@ func newPackageFromCargoMetadata(m pkg.RustCargoLockEntry, locations ...file.Loc
return p return p
} }
func cargoLockPackageURL(m pkg.RustCargoLockEntry) string {
// Cargo omits source for packages resolved from the local workspace or a
// path dependency. A cargo PURL without a repository qualifier identifies
// a package on crates.io, so emitting one for a local package can cause
// downstream scanners to match unrelated registry vulnerabilities.
if m.Source == "" {
return ""
}
return packageURL(m.Name, m.Version)
}
func newPackageFromAudit(dep *rustaudit.Package, locations ...file.Location) pkg.Package { func newPackageFromAudit(dep *rustaudit.Package, locations ...file.Location) pkg.Package {
p := pkg.Package{ p := pkg.Package{
Name: dep.Name, Name: dep.Name,

View File

@ -4,6 +4,8 @@ import (
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/anchore/syft/syft/pkg"
) )
func Test_packageURL(t *testing.T) { func Test_packageURL(t *testing.T) {
@ -31,3 +33,34 @@ func Test_packageURL(t *testing.T) {
}) })
} }
} }
func TestNewPackageFromCargoMetadataPURL(t *testing.T) {
tests := []struct {
name string
source string
want string
}{
{
name: "crates.io package",
source: "registry+https://github.com/rust-lang/crates.io-index",
want: "pkg:cargo/telemetry@0.1.0",
},
{
name: "local path package",
source: "",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := newPackageFromCargoMetadata(pkg.RustCargoLockEntry{
Name: "telemetry",
Version: "0.1.0",
Source: tt.source,
})
assert.Equal(t, tt.want, got.PURL)
})
}
}

View File

@ -351,7 +351,7 @@ func TestCargoLockWithGitDependencies(t *testing.T) {
helloWorld := pkg.Package{ helloWorld := pkg.Package{
Name: "hello_world", Name: "hello_world",
Version: "0.1.0", Version: "0.1.0",
PURL: "pkg:cargo/hello_world@0.1.0", PURL: "",
Locations: locations, Locations: locations,
Language: pkg.Rust, Language: pkg.Rust,
Type: pkg.RustPkg, Type: pkg.RustPkg,
@ -673,6 +673,77 @@ func TestCargoLockWithGitDependencies(t *testing.T) {
pkgtest.TestFileParser(t, fixture, parseCargoLock, expectedPkgs, expectedRelationships) pkgtest.TestFileParser(t, fixture, parseCargoLock, expectedPkgs, expectedRelationships)
} }
func TestCargoLockWithPathDependencies(t *testing.T) {
fixture := "testdata/path-deps/Cargo.lock"
locations := file.NewLocationSet(file.NewLocation(fixture))
app := pkg.Package{
Name: "app",
Version: "1.0.0",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "app",
Version: "1.0.0",
Dependencies: []string{
"memchr",
"telemetry",
},
},
}
memchr := pkg.Package{
Name: "memchr",
Version: "2.7.4",
PURL: "pkg:cargo/memchr@2.7.4",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "memchr",
Version: "2.7.4",
Source: "registry+https://github.com/rust-lang/crates.io-index",
Checksum: "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3",
Dependencies: []string{},
},
}
telemetry := pkg.Package{
Name: "telemetry",
Version: "0.1.0",
Locations: locations,
Language: pkg.Rust,
Type: pkg.RustPkg,
Licenses: pkg.NewLicenseSet(),
Metadata: pkg.RustCargoLockEntry{
Name: "telemetry",
Version: "0.1.0",
Dependencies: []string{},
},
}
expectedPkgs := []pkg.Package{
app,
memchr,
telemetry,
}
expectedRelationships := []artifact.Relationship{
{
From: memchr,
To: app,
Type: artifact.DependencyOfRelationship,
},
{
From: telemetry,
To: app,
Type: artifact.DependencyOfRelationship,
},
}
pkgtest.TestFileParser(t, fixture, parseCargoLock, expectedPkgs, expectedRelationships)
}
func TestCargoLockDependencySpecification(t *testing.T) { func TestCargoLockDependencySpecification(t *testing.T) {
tests := []struct { tests := []struct {
name string name string

21
syft/pkg/cataloger/rust/testdata/path-deps/Cargo.lock generated vendored Normal file
View File

@ -0,0 +1,21 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "app"
version = "1.0.0"
dependencies = [
"memchr",
"telemetry",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "telemetry"
version = "0.1.0"