mirror of
https://github.com/anchore/syft.git
synced 2026-11-01 06:56:40 +01:00
fix: keep the access path of archives discovered through a link
`discoverArchives` collects MIME hits first, and those are bare real paths. When the glob pass then found the same file through a symlink, it was dropped as a duplicate along with its access path. Java decides whether to describe an archive by its name, so a content-addressed jar (`store/3f9a1c`, linked from `lib/json-simple-1.1.1.jar`) was extracted but produced no package. This is the pnpm / nix / bazel layout. When deduping, the candidate now takes the first location that carries an access path. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
@@ -263,9 +263,9 @@ func (c *archiveCataloger) discoverArchives(resolver file.Resolver) []archiveCan
|
|||||||
}
|
}
|
||||||
|
|
||||||
var candidates []archiveCandidate
|
var candidates []archiveCandidate
|
||||||
seen := map[file.Coordinates]struct{}{}
|
seen := map[file.Coordinates]int{}
|
||||||
for _, loc := range archives {
|
for _, loc := range archives {
|
||||||
seen[loc.Coordinates] = struct{}{}
|
seen[loc.Coordinates] = len(candidates)
|
||||||
candidates = append(candidates, archiveCandidate{location: loc, sniffedAsArchive: true})
|
candidates = append(candidates, archiveCandidate{location: loc, sniffedAsArchive: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,10 +275,15 @@ func (c *archiveCataloger) discoverArchives(resolver file.Resolver) []archiveCan
|
|||||||
return candidates
|
return candidates
|
||||||
}
|
}
|
||||||
for _, loc := range namedLikeZips {
|
for _, loc := range namedLikeZips {
|
||||||
if _, dup := seen[loc.Coordinates]; dup {
|
if i, dup := seen[loc.Coordinates]; dup {
|
||||||
|
// a MIME hit is the bare real path; the name it is reached by (a link such as
|
||||||
|
// lib/dep.jar -> ../store/3f9a1c) is what name-based catalogers like java key off
|
||||||
|
if prev := candidates[i].location; prev.AccessPath == "" || prev.AccessPath == prev.RealPath {
|
||||||
|
candidates[i].location = loc
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seen[loc.Coordinates] = struct{}{}
|
seen[loc.Coordinates] = len(candidates)
|
||||||
candidates = append(candidates, archiveCandidate{location: loc})
|
candidates = append(candidates, archiveCandidate{location: loc})
|
||||||
}
|
}
|
||||||
return candidates
|
return candidates
|
||||||
|
|||||||
@@ -330,6 +330,31 @@ func TestArchiveCataloger_featureOff(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestArchiveCataloger_jarReachedThroughALinkIsIdentified(t *testing.T) {
|
||||||
|
// a content-addressed layout (pnpm, nix, bazel): the jar is stored under a hash and only the link
|
||||||
|
// to it is named like a jar
|
||||||
|
var buf bytes.Buffer
|
||||||
|
gw := gzip.NewWriter(&buf)
|
||||||
|
tw := tar.NewWriter(gw)
|
||||||
|
jar := jarBytes(t, "json-simple", "1.1.1")
|
||||||
|
require.NoError(t, tw.WriteHeader(&tar.Header{Name: "store/3f9a1c", Mode: 0o644, Size: int64(len(jar))}))
|
||||||
|
_, err := tw.Write(jar)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, tw.WriteHeader(&tar.Header{Name: "app/lib/json-simple-1.1.1.jar", Typeflag: tar.TypeSymlink, Linkname: "../../store/3f9a1c"}))
|
||||||
|
require.NoError(t, tw.Close())
|
||||||
|
require.NoError(t, gw.Close())
|
||||||
|
|
||||||
|
dir := t.TempDir()
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join(dir, "bundle.tar.gz"), buf.Bytes(), 0o644))
|
||||||
|
|
||||||
|
cfg := DefaultCreateSBOMConfig().
|
||||||
|
WithCatalogerSelection(cataloging.NewSelectionRequest().WithDefaults("java")).
|
||||||
|
WithArchiveConfig(cataloging.DefaultArchiveSearchConfig().WithMaxDepth(2))
|
||||||
|
s := scanDirWithExclusions(t, dir, cfg)
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"bundle.tar.gz|store/3f9a1c"}, packageLocations(s, "json-simple"))
|
||||||
|
}
|
||||||
|
|
||||||
func TestArchiveCataloger_descriptorRecordsArchiveConfig(t *testing.T) {
|
func TestArchiveCataloger_descriptorRecordsArchiveConfig(t *testing.T) {
|
||||||
// a consumer must be able to tell from the SBOM alone how deep archive contents were searched
|
// a consumer must be able to tell from the SBOM alone how deep archive contents were searched
|
||||||
cfg := DefaultCreateSBOMConfig().
|
cfg := DefaultCreateSBOMConfig().
|
||||||
|
|||||||
Reference in New Issue
Block a user