From c765bcdae02ed36c9953c046b93fc96f2710dfa1 Mon Sep 17 00:00:00 2001 From: Jakub Novak Date: Fri, 22 May 2026 12:57:45 +0000 Subject: [PATCH 1/2] fix(api): validate sandbox auto resume config --- .../api/internal/handlers/sandbox_create.go | 23 +++++++- .../internal/handlers/sandbox_create_test.go | 54 +++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/packages/api/internal/handlers/sandbox_create.go b/packages/api/internal/handlers/sandbox_create.go index 66434a3af1..67c9c060ea 100644 --- a/packages/api/internal/handlers/sandbox_create.go +++ b/packages/api/internal/handlers/sandbox_create.go @@ -45,8 +45,9 @@ const ( metricTemplateAlias = metrics.MetricPrefix + "template.alias" minEnvdVersionForSecureFlag = "0.2.0" // Minimum version of envd that supports secure flag - // Network validation error messages - ErrMsgDomainsRequireBlockAll = "When specifying allowed domains in allow out, you must include 'ALL_TRAFFIC' in deny out to block all other traffic." + // Validation error messages + ErrMsgDomainsRequireBlockAll = "When specifying allowed domains in allow out, you must include 'ALL_TRAFFIC' in deny out to block all other traffic." + ErrMsgAutoResumeRequiresAutoPause = "autoResume can only be enabled when autoPause is true" maxNetworkRuleDomains = 10 maxNetworkRuleTransformsPerDomain = 1 @@ -148,6 +149,12 @@ func (a *APIStore) PostSandboxes(c *gin.Context) { ) autoPause := sharedUtils.DerefOrDefault(body.AutoPause, sandbox.AutoPauseDefault) + if err := validateAutoResumeConfig(autoPause, body.AutoResume); err != nil { + a.sendAPIStoreError(c, err.Code, err.ClientMsg) + + return + } + envVars := sharedUtils.DerefOrDefault(body.EnvVars, nil) mcp := sharedUtils.DerefOrDefault(body.Mcp, nil) metadata := sharedUtils.DerefOrDefault(body.Metadata, nil) @@ -310,6 +317,18 @@ func buildAutoResumeConfig(autoResume *api.SandboxAutoResumeConfig) *types.Sandb } } +func validateAutoResumeConfig(autoPause bool, autoResume *api.SandboxAutoResumeConfig) *api.APIError { + if autoResume != nil && autoResume.Enabled && !autoPause { + return &api.APIError{ + Err: errors.New(ErrMsgAutoResumeRequiresAutoPause), + ClientMsg: ErrMsgAutoResumeRequiresAutoPause, + Code: http.StatusBadRequest, + } + } + + return nil +} + func dedupeVolumeNames(items []api.SandboxVolumeMount) []string { itemsSet := make(map[string]struct{}, len(items)) for _, item := range items { diff --git a/packages/api/internal/handlers/sandbox_create_test.go b/packages/api/internal/handlers/sandbox_create_test.go index ee789f9aca..865f0887f5 100644 --- a/packages/api/internal/handlers/sandbox_create_test.go +++ b/packages/api/internal/handlers/sandbox_create_test.go @@ -86,6 +86,60 @@ func TestBuildAutoResumeConfig(t *testing.T) { } } +func TestValidateAutoResumeConfig(t *testing.T) { + t.Parallel() + + configPtr := func(v bool) *api.SandboxAutoResumeConfig { + return &api.SandboxAutoResumeConfig{Enabled: v} + } + + tests := []struct { + name string + autoPause bool + autoResume *api.SandboxAutoResumeConfig + wantErr bool + }{ + { + name: "nil auto resume is valid with auto pause disabled", + autoPause: false, + }, + { + name: "disabled auto resume is valid with auto pause disabled", + autoPause: false, + autoResume: configPtr(false), + }, + { + name: "enabled auto resume requires auto pause", + autoPause: false, + autoResume: configPtr(true), + wantErr: true, + }, + { + name: "enabled auto resume is valid with auto pause enabled", + autoPause: true, + autoResume: configPtr(true), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got := validateAutoResumeConfig(tt.autoPause, tt.autoResume) + + if tt.wantErr { + require.NotNil(t, got) + assert.Equal(t, http.StatusBadRequest, got.Code) + assert.Equal(t, ErrMsgAutoResumeRequiresAutoPause, got.ClientMsg) + + return + } + + require.Nil(t, got) + }) + } +} + func TestValidateNetworkConfig(t *testing.T) { t.Parallel() tests := []struct { From cf9d55ff69ffba486e81904382142531ff813cbb Mon Sep 17 00:00:00 2001 From: Jakub Novak Date: Fri, 22 May 2026 13:00:33 +0000 Subject: [PATCH 2/2] chore: add telemetry call --- packages/api/internal/handlers/sandbox_create.go | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api/internal/handlers/sandbox_create.go b/packages/api/internal/handlers/sandbox_create.go index 67c9c060ea..dec182bf98 100644 --- a/packages/api/internal/handlers/sandbox_create.go +++ b/packages/api/internal/handlers/sandbox_create.go @@ -151,6 +151,7 @@ func (a *APIStore) PostSandboxes(c *gin.Context) { autoPause := sharedUtils.DerefOrDefault(body.AutoPause, sandbox.AutoPauseDefault) if err := validateAutoResumeConfig(autoPause, body.AutoResume); err != nil { a.sendAPIStoreError(c, err.Code, err.ClientMsg) + telemetry.ReportErrorByCode(ctx, err.Code, "invalid auto resume config", err.Err) return }