From 9c959c4ce735cad437b12a2fb2bf944495f081f6 Mon Sep 17 00:00:00 2001 From: chill-czar Date: Tue, 18 Aug 2026 01:17:01 +0530 Subject: [PATCH] fix(api): validate timeout is positive in PostSandboxesSandboxIDConnect --- .../api/internal/handlers/sandbox_connect.go | 6 +++ .../sandbox_timeout_validation_test.go | 40 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/packages/api/internal/handlers/sandbox_connect.go b/packages/api/internal/handlers/sandbox_connect.go index e8bf462a4a..4908acbab0 100644 --- a/packages/api/internal/handlers/sandbox_connect.go +++ b/packages/api/internal/handlers/sandbox_connect.go @@ -42,6 +42,12 @@ func (a *APIStore) PostSandboxesSandboxIDConnect(c *gin.Context, sandboxID api.S return } + if body.Timeout <= 0 { + a.sendAPIStoreError(c, http.StatusBadRequest, "Timeout must be greater than 0") + + return + } + timeout := time.Duration(body.Timeout) * time.Second if timeout > time.Duration(teamInfo.Limits.MaxLengthHours)*time.Hour { a.sendAPIStoreError(c, http.StatusBadRequest, fmt.Sprintf("Timeout cannot be greater than %d hours", teamInfo.Limits.MaxLengthHours)) diff --git a/packages/api/internal/handlers/sandbox_timeout_validation_test.go b/packages/api/internal/handlers/sandbox_timeout_validation_test.go index 592d21543c..b179385751 100644 --- a/packages/api/internal/handlers/sandbox_timeout_validation_test.go +++ b/packages/api/internal/handlers/sandbox_timeout_validation_test.go @@ -112,3 +112,43 @@ func TestSandboxFork_RejectsNonPositiveTimeout(t *testing.T) { }) } } + +// TestSandboxConnect_RejectsNonPositiveTimeout verifies that POST /sandboxes/{id}/connect +// returns 400 for zero and negative timeout values. The check runs before any +// sandbox-ID length check or snapshot lookup. +func TestSandboxConnect_RejectsNonPositiveTimeout(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + timeout int32 + }{ + {"zero", 0}, + {"negative one", -1}, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + ctx := t.Context() + store := &APIStore{} + + recorder := httptest.NewRecorder() + ginCtx, _ := gin.CreateTestContext(recorder) + + body, err := json.Marshal(api.PostSandboxesSandboxIDConnectJSONRequestBody{ + Timeout: tc.timeout, + }) + require.NoError(t, err) + + ginCtx.Request = httptest.NewRequestWithContext(ctx, http.MethodPost, "/sandboxes/abc/connect", bytes.NewReader(body)) + ginCtx.Request.Header.Set("Content-Type", "application/json") + auth.SetTeamInfoForTest(t, ginCtx, minimalTeamInfo()) + + //nolint:contextcheck // handler reads ctx from ginCtx.Request.Context(). + store.PostSandboxesSandboxIDConnect(ginCtx, "abc123") + + assertBadRequestTimeout(t, recorder) + }) + } +} +