From d5ab81dfd3ec85db17d839c0185e39d784dc798f Mon Sep 17 00:00:00 2001 From: Hongming Wang Date: Sat, 18 Apr 2026 07:48:59 -0700 Subject: [PATCH] fix(security): strip current_task from public GET /workspaces/:id (closes #955) current_task exposes live agent instructions to any caller with a valid workspace UUID. Also strips last_sample_error and workspace_dir from the public endpoint. These fields remain available through authenticated workspace-specific endpoints. Co-Authored-By: Claude Opus 4.6 (1M context) --- workspace-server/internal/handlers/handlers_test.go | 5 +++-- workspace-server/internal/handlers/workspace.go | 8 +++++--- workspace-server/internal/handlers/workspace_test.go | 5 +++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/workspace-server/internal/handlers/handlers_test.go b/workspace-server/internal/handlers/handlers_test.go index 2af65d2c..d5a56d19 100644 --- a/workspace-server/internal/handlers/handlers_test.go +++ b/workspace-server/internal/handlers/handlers_test.go @@ -1032,8 +1032,9 @@ func TestWorkspaceGet_CurrentTask(t *testing.T) { var resp map[string]interface{} json.Unmarshal(w.Body.Bytes(), &resp) - if resp["current_task"] != "Analyzing document" { - t.Errorf("expected current_task 'Analyzing document', got %v", resp["current_task"]) + // current_task stripped from public GET response (#955) + if _, exists := resp["current_task"]; exists { + t.Errorf("current_task should be stripped from public GET response") } if resp["active_tasks"] != float64(2) { t.Errorf("expected active_tasks 2, got %v", resp["active_tasks"]) diff --git a/workspace-server/internal/handlers/workspace.go b/workspace-server/internal/handlers/workspace.go index a56f2dfc..06ea73df 100644 --- a/workspace-server/internal/handlers/workspace.go +++ b/workspace-server/internal/handlers/workspace.go @@ -436,11 +436,13 @@ func (h *WorkspaceHandler) Get(c *gin.Context) { return } - // Strip financial fields — GET /workspaces/:id is on the open router. - // Any caller with a valid UUID would otherwise read billing data. - // The dedicated budget/spend endpoints are AdminAuth-gated. (#611) + // Strip sensitive fields — GET /workspaces/:id is on the open router. + // Any caller with a valid UUID would otherwise read operational data. delete(ws, "budget_limit") delete(ws, "monthly_spend") + delete(ws, "current_task") // operational surveillance risk (#955) + delete(ws, "last_sample_error") // internal error details + delete(ws, "workspace_dir") // host path disclosure c.JSON(http.StatusOK, ws) } diff --git a/workspace-server/internal/handlers/workspace_test.go b/workspace-server/internal/handlers/workspace_test.go index 74fb21a9..e2b09323 100644 --- a/workspace-server/internal/handlers/workspace_test.go +++ b/workspace-server/internal/handlers/workspace_test.go @@ -58,8 +58,9 @@ func TestWorkspaceGet_Success(t *testing.T) { if resp["runtime"] != "langgraph" { t.Errorf("expected runtime 'langgraph', got %v", resp["runtime"]) } - if resp["current_task"] != "working" { - t.Errorf("expected current_task 'working', got %v", resp["current_task"]) + // current_task is stripped from public GET response (#955) + if _, exists := resp["current_task"]; exists { + t.Errorf("current_task should be stripped from public GET response") } if err := mock.ExpectationsWereMet(); err != nil {