diff --git a/workspace-server/internal/handlers/templates.go b/workspace-server/internal/handlers/templates.go index f0fd69d7..7e87ab2a 100644 --- a/workspace-server/internal/handlers/templates.go +++ b/workspace-server/internal/handlers/templates.go @@ -295,6 +295,13 @@ func (h *TemplatesHandler) ReadFile(c *gin.Context) { c.JSON(http.StatusNotFound, gin.H{"error": "file not found (container offline, no template)"}) return } + // validateRelPath is already called above (line 260) for the container path, + // but the fallback below uses filePath directly in filepath.Join without + // any sanitization. Re-validate before the host-side read to close the gap. + if err := validateRelPath(filePath); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid path"}) + return + } fullPath := filepath.Join(templateDir, filePath) data, err := os.ReadFile(fullPath) if err != nil { diff --git a/workspace-server/internal/handlers/workspace_restart.go b/workspace-server/internal/handlers/workspace_restart.go index 686f0596..c9f123be 100644 --- a/workspace-server/internal/handlers/workspace_restart.go +++ b/workspace-server/internal/handlers/workspace_restart.go @@ -125,10 +125,15 @@ func (h *WorkspaceHandler) Restart(c *gin.Context) { template = findTemplateByName(h.configsDir, wsName) } if template != "" { - candidatePath := filepath.Join(h.configsDir, template) - if _, err := os.Stat(candidatePath); err == nil { + candidatePath, resolveErr := resolveInsideRoot(h.configsDir, template) + if resolveErr != nil { + log.Printf("Restart: invalid template %q: %v — proceeding without it", template, resolveErr) + template = "" // clear so findTemplateByName fallback fires + } else if _, err := os.Stat(candidatePath); err == nil { templatePath = candidatePath configLabel = template + } else { + log.Printf("Restart: template %q dir not found — proceeding without it", template) } }