diff --git a/workspace-server/internal/handlers/workspace_provision.go b/workspace-server/internal/handlers/workspace_provision.go index 7290c56c..f023e240 100644 --- a/workspace-server/internal/handlers/workspace_provision.go +++ b/workspace-server/internal/handlers/workspace_provision.go @@ -545,6 +545,9 @@ func (h *WorkspaceHandler) provisionWorkspaceCP(workspaceID, templatePath string if tokenErr != nil { log.Printf("CPProvisioner: failed to issue token for %s: %v", workspaceID, tokenErr) } else { - log.Printf("CPProvisioner: issued auth token for workspace %s (prefix: %s...)", workspaceID, token[:8]) + // Don't log any prefix of the token. Earlier H1 regression showed + // this slice pattern (token[:8]) panics when a helper returns a + // short value. Length alone is enough to confirm a token issued. + log.Printf("CPProvisioner: issued auth token for workspace %s (len=%d)", workspaceID, len(token)) } } diff --git a/workspace-server/internal/provisioner/cp_provisioner.go b/workspace-server/internal/provisioner/cp_provisioner.go index ca224d99..3b1a040c 100644 --- a/workspace-server/internal/provisioner/cp_provisioner.go +++ b/workspace-server/internal/provisioner/cp_provisioner.go @@ -91,14 +91,21 @@ func (p *CPProvisioner) Start(ctx context.Context, cfg WorkspaceConfig) (string, } defer resp.Body.Close() - respBody, _ := io.ReadAll(resp.Body) + // Cap body read at 64 KiB — the CP only ever returns small JSON + // responses; an unbounded read could be weaponized into log-flood + // DoS by a compromised upstream. + respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 64<<10)) var result cpProvisionResponse json.Unmarshal(respBody, &result) if resp.StatusCode != http.StatusCreated { + // Prefer the structured {"error":"..."} field. Do NOT fall back + // to string(respBody) — our logs ingest errors, and an upstream + // misconfiguration that echoed the Authorization header or + // request body into the response would leak bearer tokens. errMsg := result.Error if errMsg == "" { - errMsg = string(respBody) + errMsg = fmt.Sprintf("", len(respBody)) } return "", fmt.Errorf("cp provisioner: provision failed (%d): %s", resp.StatusCode, errMsg) }