mirror of
https://github.com/anchore/syft.git
synced 2026-10-30 14:02:26 +01:00
fix: build ArchivePath from real paths, not access paths
each `ArchivePath` segment came from `loc.Path()`, so it was the link path when an archive was reached through a symlink and the real path otherwise. That depended on how discovery happened to find the archive. The package location said `store/3f9a1c` while the files inside the same jar were recorded under `...:app/lib/json-simple-1.1.1.jar`, so a consumer couldn't join a package to its files by prefix. The chain for extracted files is now built from `RealPath`. `Traversal.Location` still carries the access path, so java's name check and `VirtualPath` metadata behave the same as before. The field comment and schema now say every segment has links resolved. Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
@@ -11,8 +11,10 @@ import (
|
||||
// cataloger task puts one on the context before running catalogers inside an archive, so a cataloger
|
||||
// such as java can describe the archive it is inside without owning the recursion.
|
||||
type Traversal struct {
|
||||
// Location is the archive file's location within its parent filesystem. Its ArchivePath names the
|
||||
// archives above it, so VirtualPath(Location) is the full chain from the scan root.
|
||||
// Location is the archive file's location within its parent filesystem, including the access path
|
||||
// it was reached by. Its ArchivePath names the archives above it, so VirtualPath(Location) is the
|
||||
// full chain from the scan root as seen through that access path. The ArchivePath of the files
|
||||
// inside it is built from the real path instead.
|
||||
Location file.Location
|
||||
|
||||
// Digests are of the archive file itself, taken during extraction; a cataloger running inside the
|
||||
|
||||
@@ -161,8 +161,9 @@ func (c *archiveCataloger) processArchive(ctx context.Context, parentResolver fi
|
||||
started := time.Now()
|
||||
|
||||
// extracted files keep the filesystem ID of where the archive was found; the nesting chain is
|
||||
// carried separately as the archive path
|
||||
archivePath := archive.VirtualPath(location)
|
||||
// carried separately as the archive path. The chain names real paths, not the link the archive was
|
||||
// reached by, so it is stable and joins to the archive's package location by prefix.
|
||||
archivePath := archive.VirtualPath(file.NewLocationFromCoordinates(location.Coordinates))
|
||||
extracted, err := archive.Extract(ctx, archiveContent, location.FileSystemID, archivePath, c.limiter, c.exclusions)
|
||||
if errors.Is(err, archive.ErrDiskLimitReached) {
|
||||
// nothing is released while this archive waits, so skip it rather than block the scan
|
||||
|
||||
@@ -724,7 +724,7 @@
|
||||
},
|
||||
"archivePath": {
|
||||
"type": "string",
|
||||
"description": "ArchivePath is the colon-delimited chain of archive paths, from the scan root, traversed to reach this file (e.g. \"app.war:WEB-INF/lib/dep.jar\"). Blank for files not found within an archive. It disambiguates identically-named files in different archives on the same filesystem. Within each segment after the first, '%' is escaped as \"%25\" and ':' as \"%3A\", so splitting on ':' recovers the archive boundaries."
|
||||
"description": "ArchivePath is the colon-delimited chain of archive paths, from the scan root, traversed to reach this file (e.g. \"app.war:WEB-INF/lib/dep.jar\"). Blank for files not found within an archive. It disambiguates identically-named files in different archives on the same filesystem. Within each segment after the first, '%' is escaped as \"%25\" and ':' as \"%3A\", so splitting on ':' recovers the archive boundaries. Each segment is an archive's real path (links resolved), relative to the segment before it."
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
@@ -2490,7 +2490,7 @@
|
||||
},
|
||||
"archivePath": {
|
||||
"type": "string",
|
||||
"description": "ArchivePath is the colon-delimited chain of archive paths, from the scan root, traversed to reach this file (e.g. \"app.war:WEB-INF/lib/dep.jar\"). Blank for files not found within an archive. It disambiguates identically-named files in different archives on the same filesystem. Within each segment after the first, '%' is escaped as \"%25\" and ':' as \"%3A\", so splitting on ':' recovers the archive boundaries."
|
||||
"description": "ArchivePath is the colon-delimited chain of archive paths, from the scan root, traversed to reach this file (e.g. \"app.war:WEB-INF/lib/dep.jar\"). Blank for files not found within an archive. It disambiguates identically-named files in different archives on the same filesystem. Within each segment after the first, '%' is escaped as \"%25\" and ':' as \"%3A\", so splitting on ':' recovers the archive boundaries. Each segment is an archive's real path (links resolved), relative to the segment before it."
|
||||
},
|
||||
"accessPath": {
|
||||
"type": "string",
|
||||
|
||||
@@ -724,7 +724,7 @@
|
||||
},
|
||||
"archivePath": {
|
||||
"type": "string",
|
||||
"description": "ArchivePath is the colon-delimited chain of archive paths, from the scan root, traversed to reach this file (e.g. \"app.war:WEB-INF/lib/dep.jar\"). Blank for files not found within an archive. It disambiguates identically-named files in different archives on the same filesystem. Within each segment after the first, '%' is escaped as \"%25\" and ':' as \"%3A\", so splitting on ':' recovers the archive boundaries."
|
||||
"description": "ArchivePath is the colon-delimited chain of archive paths, from the scan root, traversed to reach this file (e.g. \"app.war:WEB-INF/lib/dep.jar\"). Blank for files not found within an archive. It disambiguates identically-named files in different archives on the same filesystem. Within each segment after the first, '%' is escaped as \"%25\" and ':' as \"%3A\", so splitting on ':' recovers the archive boundaries. Each segment is an archive's real path (links resolved), relative to the segment before it."
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
@@ -2490,7 +2490,7 @@
|
||||
},
|
||||
"archivePath": {
|
||||
"type": "string",
|
||||
"description": "ArchivePath is the colon-delimited chain of archive paths, from the scan root, traversed to reach this file (e.g. \"app.war:WEB-INF/lib/dep.jar\"). Blank for files not found within an archive. It disambiguates identically-named files in different archives on the same filesystem. Within each segment after the first, '%' is escaped as \"%25\" and ':' as \"%3A\", so splitting on ':' recovers the archive boundaries."
|
||||
"description": "ArchivePath is the colon-delimited chain of archive paths, from the scan root, traversed to reach this file (e.g. \"app.war:WEB-INF/lib/dep.jar\"). Blank for files not found within an archive. It disambiguates identically-named files in different archives on the same filesystem. Within each segment after the first, '%' is escaped as \"%25\" and ':' as \"%3A\", so splitting on ':' recovers the archive boundaries. Each segment is an archive's real path (links resolved), relative to the segment before it."
|
||||
},
|
||||
"accessPath": {
|
||||
"type": "string",
|
||||
|
||||
@@ -349,10 +349,15 @@ func TestArchiveCataloger_jarReachedThroughALinkIsIdentified(t *testing.T) {
|
||||
|
||||
cfg := DefaultCreateSBOMConfig().
|
||||
WithCatalogerSelection(cataloging.NewSelectionRequest().WithDefaults("java")).
|
||||
WithFilesConfig(filecataloging.DefaultConfig().WithSelection(file.AllFilesSelection)).
|
||||
WithArchiveConfig(cataloging.DefaultArchiveSearchConfig().WithMaxDepth(2))
|
||||
s := scanDirWithExclusions(t, dir, cfg)
|
||||
|
||||
assert.Equal(t, []string{"bundle.tar.gz|store/3f9a1c"}, packageLocations(s, "json-simple"))
|
||||
|
||||
// the jar's own files are addressed by its real path, the same one its package location names
|
||||
manifest := file.Coordinates{RealPath: "META-INF/MANIFEST.MF", ArchivePath: "bundle.tar.gz:store/3f9a1c"}
|
||||
assert.Contains(t, s.Artifacts.FileMetadata, manifest)
|
||||
}
|
||||
|
||||
func TestArchiveCataloger_descriptorRecordsArchiveConfig(t *testing.T) {
|
||||
|
||||
@@ -15,7 +15,7 @@ type Coordinates struct {
|
||||
// FileSystemID is an ID representing an entire filesystem. For container images, this is a layer digest. For directories or a root filesystem, this is blank. A file inside an archive carries the FileSystemID of the filesystem the archive was found in.
|
||||
FileSystemID string `json:"layerID,omitempty" cyclonedx:"layerID"`
|
||||
|
||||
// ArchivePath is the colon-delimited chain of archive paths, from the scan root, traversed to reach this file (e.g. "app.war:WEB-INF/lib/dep.jar"). Blank for files not found within an archive. It disambiguates identically-named files in different archives on the same filesystem. Within each segment after the first, '%' is escaped as "%25" and ':' as "%3A", so splitting on ':' recovers the archive boundaries.
|
||||
// ArchivePath is the colon-delimited chain of archive paths, from the scan root, traversed to reach this file (e.g. "app.war:WEB-INF/lib/dep.jar"). Blank for files not found within an archive. It disambiguates identically-named files in different archives on the same filesystem. Within each segment after the first, '%' is escaped as "%25" and ':' as "%3A", so splitting on ':' recovers the archive boundaries. Each segment is an archive's real path (links resolved), relative to the segment before it.
|
||||
ArchivePath string `json:"archivePath,omitempty" cyclonedx:"archivePath"`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user