fix: make Location.Path() always the full path

`Path()` fell back to `LocalPath()` when `AccessPath` was blank, so for a nested file it returned the
full chain from a resolver but the archive-local path from a hand-built or decoded location, and
callers couldn't tell which they had.

- `Path()` now falls back to `FullNestedPath()`. Root files are unchanged
- the maven parent pom lookup joins `relativePath` onto `LocalPath()`, since enough `../` on the full
  path cleaned past the `!/` boundary into an outer path that the archive resolver then read as
  archive-local

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman
2026-10-21 11:30:31 -04:00
parent 901d7bbab2
commit b9fdabc2f6
4 changed files with 38 additions and 3 deletions
+3 -2
View File
@@ -205,12 +205,13 @@ func NewVirtualLocationFromDirectory(responsePath, responseAccessPath string, re
}
}
// Path is the path this location was accessed by, or its local path (see Coordinates.LocalPath).
// Path is the path this location was accessed by, or its full path (see Coordinates.FullNestedPath). For a
// file within an archive both are full paths, so use LocalPath for path arithmetic against the resolver.
func (l Location) Path() string {
if l.AccessPath != "" {
return l.AccessPath
}
return l.LocalPath()
return l.FullNestedPath()
}
func (l Location) String() string {
+9
View File
@@ -111,3 +111,12 @@ func TestCoordinates_LocalPath(t *testing.T) {
assert.Equal(t, "/x.txt", Coordinates{RealPath: "/data/Hello!/a.zip", FullPath: "/data/Hello!/a.zip!/x.txt"}.LocalPath())
assert.Equal(t, "/x.txt", Coordinates{RealPath: "/data/Hello!/a.zip", FullPath: "/data/Hello!/a.zip!/b.jar!/x.txt"}.LocalPath())
}
func TestLocation_Path(t *testing.T) {
nested := Coordinates{RealPath: "/a.war", FullPath: "/a.war!/lib/b.jar!/META-INF/MANIFEST.MF"}
assert.Equal(t, "/a.jar", NewLocation("/a.jar").Path())
assert.Equal(t, "/link", NewVirtualLocation("/a.jar", "/link").Path())
assert.Equal(t, nested.FullPath, NewLocationFromCoordinates(nested).Path())
assert.Equal(t, nested.FullPath, Location{LocationData: LocationData{Coordinates: nested}}.Path(), "a nested location without an access path is still full")
assert.Equal(t, nested.FullPath, NewVirtualLocationFromCoordinates(nested, nested.FullPath).Path())
}
@@ -647,7 +647,8 @@ func (r *Resolver) findParentPomByRelativePath(ctx context.Context, pom *Project
if relativePath == "" {
return nil
}
p := pomLocation.Path()
// relative to the pom within its own archive, so "../" can't clean past the archive boundary
p := pomLocation.LocalPath()
p = path.Dir(p)
p = path.Join(p, relativePath)
p = path.Clean(p)
@@ -6,9 +6,11 @@ import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/internal"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/fileresolver"
maventest "github.com/anchore/syft/syft/pkg/cataloger/java/internal/maven/test"
)
@@ -368,3 +370,25 @@ func Test_relativePathParent(t *testing.T) {
func ptr[T any](value T) *T {
return &value
}
// pathRecorder records the paths it is asked for and finds nothing.
type pathRecorder struct {
file.Resolver
paths []string
}
func (p *pathRecorder) FilesByPath(paths ...string) ([]file.Location, error) {
p.paths = append(p.paths, paths...)
return nil, nil
}
func Test_relativePathParent_staysInsideTheArchive(t *testing.T) {
recorder := &pathRecorder{}
r := NewResolver(recorder, DefaultConfig())
pom := &Project{Parent: &Parent{RelativePath: ptr("../../other")}}
nested := file.Coordinates{RealPath: "/data/outer.zip", FullPath: "/data/outer.zip!/a/pom.xml"}
r.pomLocations[pom] = file.NewVirtualLocationFromCoordinates(nested, nested.FullPath)
assert.Nil(t, r.findParentPomByRelativePath(context.Background(), pom, ID{}, nil))
assert.Equal(t, []string{"/other/pom.xml"}, recorder.paths)
}