diff --git a/modules/mesh-build-go/cmd/mesh-build-go/archive.go b/modules/mesh-build-go/cmd/mesh-build-go/archive.go index ae75363c..a83eeb3f 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/archive.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/archive.go @@ -57,6 +57,18 @@ func (m *Module) buildArchive(ctx context.Context, workspace, module, dir string if strings.Contains(filepath.ToSlash(art.From), "..") { return nil, fmt.Errorf("the archive %s is taken from %q, which lies outside the module", art.Name, art.From) } + // A link in the fetched tree must not lead out of the module: compare where both really are. + realDir, err := filepath.EvalSymlinks(dir) + if err != nil { + return nil, err + } + realRoot, err := filepath.EvalSymlinks(root) + if err != nil { + return nil, err + } + if rel, err := filepath.Rel(realDir, realRoot); err != nil || rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) { + return nil, fmt.Errorf("the archive %s is taken from %q, which lies outside the module: it resolves to %s, and the module is %s", art.Name, art.From, realRoot, realDir) + } return m.packInto(workspace, module, art.Name, root) } diff --git a/modules/mesh-build-go/cmd/mesh-build-go/archive_test.go b/modules/mesh-build-go/cmd/mesh-build-go/archive_test.go index 40960f03..aa4716a2 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/archive_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/archive_test.go @@ -195,3 +195,26 @@ func TestAnImageArtifactIsStillRefusedByName(t *testing.T) { t.Errorf("the step ended %s: %q", st.State, st.Reason) } } + +// A symlinked parent in the fetched tree must not take the archive outside the module (review of issue 1589): the +// link is made at run time, in a directory outside the workspace, and the step fails naming both paths. +func TestAnArchiveBehindASymlinkedParentIsRefused(t *testing.T) { + m, states, _ := archiveModule(t, `{"module":"demo","version":"1","build":{"artifacts":[{"name":"files","kind":"archive","from":"link/secret"}]}}`) + outside := t.TempDir() + if err := os.MkdirAll(filepath.Join(outside, "secret"), 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(outside, "secret", "key"), []byte("private"), 0o600); err != nil { + t.Fatal(err) + } + if err := os.Symlink(outside, filepath.Join(m.Work, "ws1", "tree", "modules", "demo", "link")); err != nil { + t.Fatal(err) + } + _, st := runBuild(t, m, states, "files") + if st.State != delivery.StepFailed || !strings.Contains(st.Reason, "outside") || !strings.Contains(st.Reason, outside) { + t.Errorf("the step ended %s: %q", st.State, st.Reason) + } + if entries, _ := os.ReadDir(filepath.Join(m.Work, "ws1", "artifacts")); len(entries) != 0 { + t.Errorf("an archive of outside files was left: %v", entries) + } +}