diff --git a/syft/pkg/cataloger/rust/package.go b/syft/pkg/cataloger/rust/package.go index 9ed04696d..aebf04540 100644 --- a/syft/pkg/cataloger/rust/package.go +++ b/syft/pkg/cataloger/rust/package.go @@ -14,7 +14,7 @@ func newPackageFromCargoMetadata(m pkg.RustCargoLockEntry, locations ...file.Loc Name: m.Name, Version: m.Version, Locations: file.NewLocationSet(locations...), - PURL: packageURL(m.Name, m.Version), + PURL: cargoLockPackageURL(m), Language: pkg.Rust, Type: pkg.RustPkg, Metadata: m, @@ -25,6 +25,18 @@ func newPackageFromCargoMetadata(m pkg.RustCargoLockEntry, locations ...file.Loc 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 { p := pkg.Package{ Name: dep.Name, diff --git a/syft/pkg/cataloger/rust/package_test.go b/syft/pkg/cataloger/rust/package_test.go index 01f1f9a2f..c1cc84125 100644 --- a/syft/pkg/cataloger/rust/package_test.go +++ b/syft/pkg/cataloger/rust/package_test.go @@ -4,6 +4,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/anchore/syft/syft/pkg" ) 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) + }) + } +} diff --git a/syft/pkg/cataloger/rust/parse_cargo_lock_test.go b/syft/pkg/cataloger/rust/parse_cargo_lock_test.go index aa13b3755..3b74e4b67 100644 --- a/syft/pkg/cataloger/rust/parse_cargo_lock_test.go +++ b/syft/pkg/cataloger/rust/parse_cargo_lock_test.go @@ -351,7 +351,7 @@ func TestCargoLockWithGitDependencies(t *testing.T) { helloWorld := pkg.Package{ Name: "hello_world", Version: "0.1.0", - PURL: "pkg:cargo/hello_world@0.1.0", + PURL: "", Locations: locations, Language: pkg.Rust, Type: pkg.RustPkg, @@ -673,6 +673,77 @@ func TestCargoLockWithGitDependencies(t *testing.T) { 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) { tests := []struct { name string diff --git a/syft/pkg/cataloger/rust/testdata/path-deps/Cargo.lock b/syft/pkg/cataloger/rust/testdata/path-deps/Cargo.lock new file mode 100644 index 000000000..388954608 --- /dev/null +++ b/syft/pkg/cataloger/rust/testdata/path-deps/Cargo.lock @@ -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"