diff --git a/local.go b/local.go index 4bafa62..d0bf6e8 100644 --- a/local.go +++ b/local.go @@ -52,12 +52,23 @@ func (f *LocalFetcher) walk(p *POM, dir string) { return } rel := p.Parent.LocalPath() - if rel == "" { + if rel == "" || filepath.IsAbs(rel) { return } - path := filepath.Join(dir, rel) - if fi, err := os.Stat(path); err == nil && fi.IsDir() { + path := filepath.Clean(filepath.Join(dir, rel)) + fi, err := os.Lstat(path) + if err != nil { + return + } + if fi.Mode()&os.ModeSymlink != 0 { + return + } + if fi.IsDir() { path = filepath.Join(path, "pom.xml") + fi, err = os.Lstat(path) + if err != nil || fi.Mode()&os.ModeSymlink != 0 { + return + } } parent, err := readPOMFile(path) if err != nil { diff --git a/local_test.go b/local_test.go index 95b1c92..06b0f10 100644 --- a/local_test.go +++ b/local_test.go @@ -2,6 +2,8 @@ package pom import ( "context" + "os" + "path/filepath" "testing" ) @@ -80,3 +82,58 @@ func TestParentLocalPath(t *testing.T) { } } } + +func TestLocalFetcherRejectsAbsoluteRelativePath(t *testing.T) { + tmp := t.TempDir() + childDir := filepath.Join(tmp, "child") + _ = os.MkdirAll(childDir, 0o755) + + targetPOM := filepath.Join(tmp, "target", "pom.xml") + _ = os.MkdirAll(filepath.Dir(targetPOM), 0o755) + _ = os.WriteFile(targetPOM, []byte(`org.evilevil1.0`), 0o644) + + absPath := targetPOM + child := &POM{ + GroupID: "org.example", + ArtifactID: "child", + Version: "1.0", + Parent: &Parent{GroupID: "org.evil", ArtifactID: "evil", Version: "1.0", RelativePath: &absPath}, + } + + f := NewLocalFetcherFrom(child, childDir) + _, err := f.Fetch(context.Background(), GAV{"org.evil", "evil", "1.0"}) + if err == nil { + t.Error("expected error: absolute relativePath should be rejected") + } +} + +func TestLocalFetcherRejectsSymlink(t *testing.T) { + tmp := t.TempDir() + childDir := filepath.Join(tmp, "child") + _ = os.MkdirAll(childDir, 0o755) + + // Create a target POM outside the project tree + outsideDir := filepath.Join(tmp, "outside") + _ = os.MkdirAll(outsideDir, 0o755) + _ = os.WriteFile(filepath.Join(outsideDir, "pom.xml"), []byte(`org.evilevil1.0`), 0o644) + + // Create symlink from child/parent -> outside + symlink := filepath.Join(childDir, "parent") + if err := os.Symlink(outsideDir, symlink); err != nil { + t.Skipf("symlinks not supported: %v", err) + } + + rel := "parent" + child := &POM{ + GroupID: "org.example", + ArtifactID: "child", + Version: "1.0", + Parent: &Parent{GroupID: "org.evil", ArtifactID: "evil", Version: "1.0", RelativePath: &rel}, + } + + f := NewLocalFetcherFrom(child, childDir) + _, err := f.Fetch(context.Background(), GAV{"org.evil", "evil", "1.0"}) + if err == nil { + t.Error("expected error: symlink traversal should be rejected") + } +}