fix: don't index nested packages under the outer archive path

`indexedPaths` added each location's `RealPath`, which for a nested file is the outermost archive. So
`PackagesByPath("/opt/x.jar")` returned every package found inside it at any depth, a root OS package
owning that file got ownership-overlap edges to all of them, and
`ExcludeBinaryPackagesWithFileOwnershipOverlap` then deleted nested binary, ELF and JVM packages.

Packages are now indexed by full path and access path only. An archive's own package still sits at
root coordinates and is found by its path; what's inside it stays linked to it by CONTAINS edges
rather than by ownership of the archive file.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman
2026-10-21 11:35:21 -04:00
parent f0129112be
commit 01546d4e06
3 changed files with 25 additions and 13 deletions
@@ -103,7 +103,9 @@ func TestOwnershipByFilesRelationship(t *testing.T) {
},
},
{
name: "top-level-owner-of-an-archive-owns-packages-within-it",
// the archive's own package (when it has one) is found by its path; what is inside it is
// linked to that package by CONTAINS, not owned by whoever owns the archive file
name: "top-level-owner-of-an-archive-does-not-own-packages-within-it",
setup: func(t testing.TB) ([]pkg.Package, []artifact.Relationship) {
parent := pkg.Package{
Locations: file.NewLocationSet(file.NewLocation("/var/lib/rpm/Packages")),
@@ -123,14 +125,7 @@ func TestOwnershipByFilesRelationship(t *testing.T) {
}
child.SetID()
relationship := artifact.Relationship{
From: parent,
To: child,
Type: artifact.OwnershipByFileOverlapRelationship,
Data: ownershipByFilesMetadata{Files: []string{"/opt/bundle.tar"}},
}
return []pkg.Package{parent, child}, []artifact.Relationship{relationship}
return []pkg.Package{parent, child}, nil
},
},
{
+4 -4
View File
@@ -62,7 +62,7 @@ func (c *Collection) Package(id artifact.ID) *Package {
}
// PackagesByPath returns all packages that were discovered from the given path: a file's full path or
// access path, or the top path of an archive, which answers with every package found within it.
// access path. A package found within an archive is not returned for the archive's own path.
func (c *Collection) PackagesByPath(path string) []Package {
c.lock.RLock()
defer c.lock.RUnlock()
@@ -164,12 +164,12 @@ func (c *Collection) addPathsToIndex(p Package) {
}
}
// indexedPaths are the paths a package is found by: each location's full path and access path, and
// the top path of an archive it was found within.
// indexedPaths are the paths a package is found by: each location's full path and access path. The real
// path of a nested location is the outermost archive, so it is left out.
func indexedPaths(p Package) []string {
paths := strset.New()
for _, l := range p.Locations.ToSlice() {
paths.Add(l.RealPath, l.AccessPath, l.FullNestedPath())
paths.Add(l.AccessPath, l.FullNestedPath())
}
paths.Remove("")
return paths.List()
+17
View File
@@ -433,6 +433,23 @@ func TestCatalog_MergeRecords(t *testing.T) {
}
}
func TestCatalog_PackagesByPath_nestedPackagesAreNotFoundByTheOuterArchive(t *testing.T) {
jar := Package{Name: "jar", Type: JavaPkg, Locations: file.NewLocationSet(file.NewLocation("/opt/x.jar"))}
nested := file.Coordinates{RealPath: "/opt/x.jar", FullPath: "/opt/x.jar!/lib/a.so"}
so := Package{Name: "so", Type: BinaryPkg, Locations: file.NewLocationSet(file.NewLocationFromCoordinates(nested))}
c := NewCollection(jar, so)
names := func(pkgs []Package) []string {
var out []string
for _, p := range pkgs {
out = append(out, p.Name)
}
return out
}
assert.Equal(t, []string{"jar"}, names(c.PackagesByPath("/opt/x.jar")))
assert.Equal(t, []string{"so"}, names(c.PackagesByPath(nested.FullPath)))
}
func TestCatalog_EnumerateNilCatalog(t *testing.T) {
var c *Collection
assert.Empty(t, c.Enumerate())