From d60d71ccdd71fd3dd9e381b2fb3607a356ddc4d4 Mon Sep 17 00:00:00 2001 From: Molecule AI CP-BE Date: Tue, 21 Apr 2026 03:56:47 +0000 Subject: [PATCH] fix(security): CWE-22 path traversal in copyFilesToContainer and deleteViaEphemeral MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit copyFilesToContainer: validate each map key with filepath.Clean before using it in the tar header Name field. Reject absolute paths and any path containing "..". Use filepath.Join(destPath, clean) so the tar entry Name is always a safe relative path inside destPath. Also apply the same sanitisation to the parent-directory entries written for the tar. deleteViaEphemeral: call validateRelPath(filePath) before constructing the rm command so a path-traversal sequence cannot escape the /configs bind mount. Both functions are reachable by callers with org-token auth — an attacker with a valid org token could craft a file map with "../" entries to write outside /configs, or pass traversal paths to rm. Co-Authored-By: Claude Sonnet 4.6 --- .../internal/handlers/container_files.go | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/workspace-server/internal/handlers/container_files.go b/workspace-server/internal/handlers/container_files.go index 838e09eee..5d920a470 100644 --- a/workspace-server/internal/handlers/container_files.go +++ b/workspace-server/internal/handlers/container_files.go @@ -73,9 +73,19 @@ func (h *TemplatesHandler) copyFilesToContainer(ctx context.Context, containerNa createdDirs := map[string]bool{} for name, content := range files { + // CWE-22: reject absolute paths and path-traversal sequences + // before using the name in the tar header. + clean := filepath.Clean(name) + if filepath.IsAbs(clean) || strings.Contains(clean, "..") { + return fmt.Errorf("path traversal blocked: %s", name) + } + // Use the safe, cleaned name joined with destPath so the tar + // header Name is always a relative path inside destPath. + safeName := filepath.Join(destPath, clean) + // Create parent directories in tar (deduplicated) - dir := filepath.Dir(name) - if dir != "." && !createdDirs[dir] { + dir := filepath.Dir(safeName) + if dir != destPath && !createdDirs[dir] { tw.WriteHeader(&tar.Header{ Typeflag: tar.TypeDir, Name: dir + "/", @@ -86,7 +96,7 @@ func (h *TemplatesHandler) copyFilesToContainer(ctx context.Context, containerNa data := []byte(content) header := &tar.Header{ - Name: name, + Name: safeName, Mode: 0644, Size: int64(len(data)), } @@ -143,6 +153,12 @@ func (h *TemplatesHandler) deleteViaEphemeral(ctx context.Context, volumeName, f return fmt.Errorf("docker not available") } + // CWE-22: validate filePath before constructing the rm command so + // a path-traversal sequence cannot escape /configs. + if err := validateRelPath(filePath); err != nil { + return err + } + resp, err := h.docker.ContainerCreate(ctx, &container.Config{ Image: "alpine:latest", Cmd: []string{"rm", "-rf", "/configs/" + filePath},