mirror of
https://github.com/anchore/syft.git
synced 2026-11-02 15:36:41 +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
|
// 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.
|
// such as java can describe the archive it is inside without owning the recursion.
|
||||||
type Traversal struct {
|
type Traversal struct {
|
||||||
// Location is the archive file's location within its parent filesystem. Its ArchivePath names the
|
// Location is the archive file's location within its parent filesystem, including the access path
|
||||||
// archives above it, so VirtualPath(Location) is the full chain from the scan root.
|
// 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
|
Location file.Location
|
||||||
|
|
||||||
// Digests are of the archive file itself, taken during extraction; a cataloger running inside the
|
// 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()
|
started := time.Now()
|
||||||
|
|
||||||
// extracted files keep the filesystem ID of where the archive was found; the nesting chain is
|
// extracted files keep the filesystem ID of where the archive was found; the nesting chain is
|
||||||
// carried separately as the archive path
|
// carried separately as the archive path. The chain names real paths, not the link the archive was
|
||||||
archivePath := archive.VirtualPath(location)
|
// 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)
|
extracted, err := archive.Extract(ctx, archiveContent, location.FileSystemID, archivePath, c.limiter, c.exclusions)
|
||||||
if errors.Is(err, archive.ErrDiskLimitReached) {
|
if errors.Is(err, archive.ErrDiskLimitReached) {
|
||||||
// nothing is released while this archive waits, so skip it rather than block the scan
|
// nothing is released while this archive waits, so skip it rather than block the scan
|
||||||
|
|||||||
@@ -724,7 +724,7 @@
|
|||||||
},
|
},
|
||||||
"archivePath": {
|
"archivePath": {
|
||||||
"type": "string",
|
"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",
|
"type": "object",
|
||||||
@@ -2490,7 +2490,7 @@
|
|||||||
},
|
},
|
||||||
"archivePath": {
|
"archivePath": {
|
||||||
"type": "string",
|
"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": {
|
"accessPath": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@@ -724,7 +724,7 @@
|
|||||||
},
|
},
|
||||||
"archivePath": {
|
"archivePath": {
|
||||||
"type": "string",
|
"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",
|
"type": "object",
|
||||||
@@ -2490,7 +2490,7 @@
|
|||||||
},
|
},
|
||||||
"archivePath": {
|
"archivePath": {
|
||||||
"type": "string",
|
"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": {
|
"accessPath": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@@ -349,10 +349,15 @@ func TestArchiveCataloger_jarReachedThroughALinkIsIdentified(t *testing.T) {
|
|||||||
|
|
||||||
cfg := DefaultCreateSBOMConfig().
|
cfg := DefaultCreateSBOMConfig().
|
||||||
WithCatalogerSelection(cataloging.NewSelectionRequest().WithDefaults("java")).
|
WithCatalogerSelection(cataloging.NewSelectionRequest().WithDefaults("java")).
|
||||||
|
WithFilesConfig(filecataloging.DefaultConfig().WithSelection(file.AllFilesSelection)).
|
||||||
WithArchiveConfig(cataloging.DefaultArchiveSearchConfig().WithMaxDepth(2))
|
WithArchiveConfig(cataloging.DefaultArchiveSearchConfig().WithMaxDepth(2))
|
||||||
s := scanDirWithExclusions(t, dir, cfg)
|
s := scanDirWithExclusions(t, dir, cfg)
|
||||||
|
|
||||||
assert.Equal(t, []string{"bundle.tar.gz|store/3f9a1c"}, packageLocations(s, "json-simple"))
|
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) {
|
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 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"`
|
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"`
|
ArchivePath string `json:"archivePath,omitempty" cyclonedx:"archivePath"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user