diff --git a/pkg/plugins/client.go b/pkg/plugins/client.go index 642b2dc68..1cee55d3f 100644 --- a/pkg/plugins/client.go +++ b/pkg/plugins/client.go @@ -240,6 +240,8 @@ func (c *Client) Unzip(pName, pVersion string) error { return nil } + // Unzip as a generic archive if the module unzip fails. + // This is useful for plugins that have vendor directories or other structures. return c.unzipArchive(pName, pVersion) } @@ -280,24 +282,48 @@ func unzipFile(f *zipa.File, dest string) error { defer func() { _ = rc.Close() }() + // Split to discard the first part of the path, which is [organization]-[project]-[release commit sha1] when the archive is a Yaegi go plugin with vendoring. pathParts := strings.SplitN(f.Name, "/", 2) - p := filepath.Join(dest, pathParts[1]) + if len(pathParts) < 2 { + return fmt.Errorf("no root directory: %s", f.Name) + } + + // Validate and sanitize the file path. + cleanName := filepath.Clean(pathParts[1]) + if strings.Contains(cleanName, "..") { + return fmt.Errorf("invalid file path in archive: %s", f.Name) + } + + filePath := filepath.Join(dest, cleanName) + absFilePath, err := filepath.Abs(filePath) + if err != nil { + return fmt.Errorf("resolving file path: %w", err) + } + + absDest, err := filepath.Abs(dest) + if err != nil { + return fmt.Errorf("resolving destination directory: %w", err) + } + + if !strings.HasPrefix(absFilePath, absDest) { + return fmt.Errorf("file path escapes destination directory: %s", absFilePath) + } if f.FileInfo().IsDir() { - err = os.MkdirAll(p, f.Mode()) + err = os.MkdirAll(filePath, f.Mode()) if err != nil { - return fmt.Errorf("unable to create archive directory %s: %w", p, err) + return fmt.Errorf("unable to create archive directory %s: %w", filePath, err) } return nil } - err = os.MkdirAll(filepath.Dir(p), 0o750) + err = os.MkdirAll(filepath.Dir(filePath), 0o750) if err != nil { - return fmt.Errorf("unable to create archive directory %s for file %s: %w", filepath.Dir(p), p, err) + return fmt.Errorf("unable to create archive directory %s for file %s: %w", filepath.Dir(filePath), filePath, err) } - elt, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + elt, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) if err != nil { return err }