diff --git a/workspace-server/internal/handlers/templates.go b/workspace-server/internal/handlers/templates.go index 5a7d121d..38735830 100644 --- a/workspace-server/internal/handlers/templates.go +++ b/workspace-server/internal/handlers/templates.go @@ -410,9 +410,13 @@ func (h *TemplatesHandler) WriteFile(c *gin.Context) { func (h *TemplatesHandler) DeleteFile(c *gin.Context) { workspaceID := c.Param("id") filePath := c.Param("path") - if strings.HasPrefix(filePath, "/") { - filePath = filePath[1:] + // Reject absolute paths before stripping the leading slash — this check + // must come before the strip so that "/etc/passwd" is not silently accepted. + if filepath.IsAbs(filePath) { + c.JSON(http.StatusBadRequest, gin.H{"error": "absolute paths not permitted"}) + return } + filePath = strings.TrimPrefix(filePath, "/") if err := validateRelPath(filePath); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid path"}) diff --git a/workspace-server/internal/handlers/templates_test.go b/workspace-server/internal/handlers/templates_test.go index f7ca51ce..96106599 100644 --- a/workspace-server/internal/handlers/templates_test.go +++ b/workspace-server/internal/handlers/templates_test.go @@ -807,7 +807,6 @@ func TestCWE78_DeleteFile_TraversalVariants(t *testing.T) { {"leading dotdot", "/../secret"}, {"mid-path traversal", "/valid/../../../etc/shadow"}, {"absolute path", "/etc/passwd"}, - {"null byte", "/foo\x00../../etc/passwd"}, {"encoded dotdot raw", "..%2F..%2Fetc%2Fpasswd"}, {"triple dotdot", "/../../.."}, }