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) <noreply@anthropic.com>
This commit is contained in:
Hongming Wang 2026-04-18 07:48:59 -07:00
parent 0d538ab27a
commit d5ab81dfd3
3 changed files with 11 additions and 7 deletions

View File

@ -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"])

View File

@ -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)
}

View File

@ -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 {