From 7090eab0d52fc291c36dfe93b06009e4503d224c Mon Sep 17 00:00:00 2001 From: Molecule AI Core Platform Lead Date: Sat, 9 May 2026 21:01:40 +0000 Subject: [PATCH] fix(workspace-server): sanitize err.Error() leaks in CascadeDelete and OrgImport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [core-lead-agent] Closes Core-Security audit finding (2026-05-09 audit cycle, MEDIUM): 1. workspace-server/internal/handlers/workspace_crud.go:335 `DELETE /workspaces/:id` returned `err.Error()` verbatim in the 500 body, leaking wrapped lib/pq driver strings (schema column names, index hints) to HTTP clients. Replaced with sanitized message; raw error already logged server-side via the existing log.Printf immediately above. 2. workspace-server/internal/handlers/org.go:610 `OrgImport` echoed the user-supplied `body.Dir` verbatim in the 404 "org template not found: %s" response. Path traversal is already blocked by resolveInsideRoot earlier in the handler, but echoing raw input back lets a client probe filesystem layout (404-with-echo vs. 400-from-resolve is itself a signal). Dropped the input from the client-facing message; preserved full context in a new log.Printf (orgFile path + the requested body.Dir) for operator triage. Both fixes preserve operator-side diagnostics (logs unchanged in content, only client-facing JSON sanitized). No behavior change for legitimate clients — error type, status code, and JSON shape all stay the same. Tier: low. Defensive hardening only; reduces info-disclosure surface without altering control-flow or auth gates. --- workspace-server/internal/handlers/org.go | 11 ++++++++++- workspace-server/internal/handlers/workspace_crud.go | 8 +++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/workspace-server/internal/handlers/org.go b/workspace-server/internal/handlers/org.go index 233cc69f..8b5c4585 100644 --- a/workspace-server/internal/handlers/org.go +++ b/workspace-server/internal/handlers/org.go @@ -607,7 +607,16 @@ func (h *OrgHandler) Import(c *gin.Context) { orgFile := filepath.Join(orgBaseDir, "org.yaml") data, err := os.ReadFile(orgFile) if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("org template not found: %s", body.Dir)}) + // Audit 2026-05-09 (Core-Security): the prior message echoed + // the user-supplied `body.Dir` verbatim. Path traversal is + // already blocked by resolveInsideRoot above, but echoing + // the raw input back lets a client probe for the existence + // of relative paths inside h.orgDir (a 404 with the input + // vs. a 400 from resolveInsideRoot is itself a signal). + // Drop the input from the message; log full context server- + // side via the resolved path for operator triage. + log.Printf("OrgImport: failed to read %s (requested dir=%q): %v", orgFile, body.Dir, err) + c.JSON(http.StatusNotFound, gin.H{"error": "org template not found"}) return } // Expand !include directives before unmarshal. Splits org.yaml diff --git a/workspace-server/internal/handlers/workspace_crud.go b/workspace-server/internal/handlers/workspace_crud.go index cc487a4a..c2674d32 100644 --- a/workspace-server/internal/handlers/workspace_crud.go +++ b/workspace-server/internal/handlers/workspace_crud.go @@ -331,8 +331,14 @@ func (h *WorkspaceHandler) Delete(c *gin.Context) { // stay in this handler. descendantIDs, stopErrs, err := h.CascadeDelete(ctx, id) if err != nil { + // Audit 2026-05-09 (Core-Security): raw `err.Error()` here was + // exposed to HTTP clients verbatim, including wrapped lib/pq + // driver strings that disclose schema column names + index + // hints. Log full error server-side; return a sanitized message + // to the client. Operators trace via the log line below using + // the workspace id. log.Printf("Delete: CascadeDelete(%s) failed: %v", id, err) - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error processing delete request"}) return } allIDs := append([]string{id}, descendantIDs...)