diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index d83f58d3..eeada322 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -9,6 +9,10 @@ on: tags: - '*' +concurrency: + group: ci-${{ github.head_ref || github.ref }}-${{ github.repository }} + cancel-in-progress: true + jobs: docker: runs-on: ubuntu-latest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 17e3c809..48fe2cf8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,6 +9,10 @@ on: tags: - '*' +concurrency: + group: ci-tests-${{ github.head_ref || github.ref }}-${{ github.repository }} + cancel-in-progress: true + jobs: ubuntu-latest: runs-on: ubuntu-latest diff --git a/api/gallery.go b/api/gallery.go index 5378c7bc..cb165f84 100644 --- a/api/gallery.go +++ b/api/gallery.go @@ -86,6 +86,8 @@ func (g *galleryApplier) start(c context.Context, cm *ConfigMerger) { continue } + config.Files = append(config.Files, op.req.AdditionalFiles...) + if err := gallery.Apply(g.modelPath, op.req.Name, &config); err != nil { updateError(err) continue @@ -106,8 +108,9 @@ func (g *galleryApplier) start(c context.Context, cm *ConfigMerger) { // endpoints type ApplyGalleryModelRequest struct { - URL string `json:"url"` - Name string `json:"name"` + URL string `json:"url"` + Name string `json:"name"` + AdditionalFiles []gallery.File `json:"files"` } func getOpStatus(g *galleryApplier) func(c *fiber.Ctx) error { diff --git a/pkg/gallery/models.go b/pkg/gallery/models.go index bd9e1371..8c61380c 100644 --- a/pkg/gallery/models.go +++ b/pkg/gallery/models.go @@ -50,9 +50,9 @@ type Config struct { } type File struct { - Filename string `yaml:"filename"` - SHA256 string `yaml:"sha256"` - URI string `yaml:"uri"` + Filename string `yaml:"filename" json:"filename"` + SHA256 string `yaml:"sha256" json:"sha256"` + URI string `yaml:"uri" json:"uri"` } type PromptTemplate struct { @@ -77,6 +77,21 @@ func ReadConfigFile(filePath string) (*Config, error) { return &config, nil } +func inTrustedRoot(path string, trustedRoot string) error { + for path != "/" { + path = filepath.Dir(path) + if path == trustedRoot { + return nil + } + } + return fmt.Errorf("path is outside of trusted root") +} + +func verifyPath(path, basePath string) error { + c := filepath.Clean(filepath.Join(basePath, path)) + return inTrustedRoot(c, basePath) +} + func Apply(basePath, nameOverride string, config *Config) error { // Create base path if it doesn't exist err := os.MkdirAll(basePath, 0755) @@ -88,6 +103,9 @@ func Apply(basePath, nameOverride string, config *Config) error { for _, file := range config.Files { log.Debug().Msgf("Checking %q exists and matches SHA", file.Filename) + if err := verifyPath(file.Filename, basePath); err != nil { + return err + } // Create file path filePath := filepath.Join(basePath, file.Filename) @@ -173,6 +191,9 @@ func Apply(basePath, nameOverride string, config *Config) error { // Write prompt template contents to separate files for _, template := range config.PromptTemplates { + if err := verifyPath(template.Name+".tmpl", basePath); err != nil { + return err + } // Create file path filePath := filepath.Join(basePath, template.Name+".tmpl") @@ -195,6 +216,10 @@ func Apply(basePath, nameOverride string, config *Config) error { name = nameOverride } + if err := verifyPath(name+".yaml", basePath); err != nil { + return err + } + configFilePath := filepath.Join(basePath, name+".yaml") // Read and update config file as map[string]interface{} diff --git a/pkg/gallery/models_test.go b/pkg/gallery/models_test.go index 123948ad..980b3a96 100644 --- a/pkg/gallery/models_test.go +++ b/pkg/gallery/models_test.go @@ -26,5 +26,30 @@ var _ = Describe("Model test", func() { Expect(err).ToNot(HaveOccurred()) } }) + It("renames model correctly", func() { + tempdir, err := os.MkdirTemp("", "test") + Expect(err).ToNot(HaveOccurred()) + defer os.RemoveAll(tempdir) + c, err := ReadConfigFile(filepath.Join(os.Getenv("FIXTURES"), "gallery_simple.yaml")) + Expect(err).ToNot(HaveOccurred()) + + err = Apply(tempdir, "foo", c) + Expect(err).ToNot(HaveOccurred()) + + for _, f := range []string{"cerebras", "cerebras-completion.tmpl", "cerebras-chat.tmpl", "foo.yaml"} { + _, err = os.Stat(filepath.Join(tempdir, f)) + Expect(err).ToNot(HaveOccurred()) + } + }) + It("catches path traversals", func() { + tempdir, err := os.MkdirTemp("", "test") + Expect(err).ToNot(HaveOccurred()) + defer os.RemoveAll(tempdir) + c, err := ReadConfigFile(filepath.Join(os.Getenv("FIXTURES"), "gallery_simple.yaml")) + Expect(err).ToNot(HaveOccurred()) + + err = Apply(tempdir, "../../../foo", c) + Expect(err).To(HaveOccurred()) + }) }) })