From 4cd65cff83991e9ed1f987af6a29709f4d5e5f70 Mon Sep 17 00:00:00 2001 From: Vishnu Kothakapu Date: Mon, 3 Aug 2026 23:32:13 +0530 Subject: [PATCH] Fix out-of-bounds panic on empty DeployTargets in plugins Signed-off-by: Vishnu Kothakapu --- pkg/plugin/sdk/deployment.go | 4 ++++ pkg/plugin/sdk/livestate.go | 4 ++++ pkg/plugin/sdk/livestate_test.go | 2 +- pkg/plugin/sdk/planpreview.go | 4 ++++ pkg/plugin/sdk/planpreview_test.go | 2 +- pkg/plugin/sdk/plugin.go | 7 +++++++ 6 files changed, 21 insertions(+), 2 deletions(-) diff --git a/pkg/plugin/sdk/deployment.go b/pkg/plugin/sdk/deployment.go index 5f87b32d93..11cb37f899 100644 --- a/pkg/plugin/sdk/deployment.go +++ b/pkg/plugin/sdk/deployment.go @@ -193,6 +193,10 @@ func (s *DeploymentPluginServiceServer[Config, DeployTargetConfig, ApplicationCo // Get the deploy targets set on the deployment from the piped plugin config. dtNames := request.GetInput().GetDeployment().GetDeployTargets(s.config.Name) + if len(dtNames) == 0 { + return nil, status.Errorf(codes.InvalidArgument, "no deploy targets provided") + } + deployTargets := make([]*DeployTarget[DeployTargetConfig], 0, len(dtNames)) for _, name := range dtNames { dt, ok := s.deployTargets[name] diff --git a/pkg/plugin/sdk/livestate.go b/pkg/plugin/sdk/livestate.go index 7c0678a8f6..c5ff2692a3 100644 --- a/pkg/plugin/sdk/livestate.go +++ b/pkg/plugin/sdk/livestate.go @@ -54,6 +54,10 @@ func (s *LivestatePluginServer[Config, DeployTargetConfig, ApplicationConfigSpec // GetLivestate returns the live state of the resources in the given application. func (s *LivestatePluginServer[Config, DeployTargetConfig, ApplicationConfigSpec]) GetLivestate(ctx context.Context, request *livestate.GetLivestateRequest) (*livestate.GetLivestateResponse, error) { // Get the deploy targets set on the deployment from the piped plugin config. + if len(request.GetDeployTargets()) == 0 { + return nil, status.Errorf(codes.InvalidArgument, "no deploy targets provided") + } + deployTargets := make([]*DeployTarget[DeployTargetConfig], 0, len(request.GetDeployTargets())) for _, name := range request.GetDeployTargets() { dt, ok := s.deployTargets[name] diff --git a/pkg/plugin/sdk/livestate_test.go b/pkg/plugin/sdk/livestate_test.go index 10e0e6c8f6..09d21a6079 100644 --- a/pkg/plugin/sdk/livestate_test.go +++ b/pkg/plugin/sdk/livestate_test.go @@ -125,7 +125,7 @@ spec: {} result: &GetLivestateResponse{}, err: errors.New("some error"), expectErr: true, - expectedStatus: codes.Internal, + expectedStatus: codes.InvalidArgument, }, } diff --git a/pkg/plugin/sdk/planpreview.go b/pkg/plugin/sdk/planpreview.go index 0970ac8074..89031b8ae2 100644 --- a/pkg/plugin/sdk/planpreview.go +++ b/pkg/plugin/sdk/planpreview.go @@ -50,6 +50,10 @@ func (s *PlanPreviewPluginServer[Config, DeployTargetConfig, ApplicationConfigSp // GetPlanPreview returns the plan preview of the resources in the given application. func (s *PlanPreviewPluginServer[Config, DeployTargetConfig, ApplicationConfigSpec]) GetPlanPreview(ctx context.Context, request *planpreview.GetPlanPreviewRequest) (*planpreview.GetPlanPreviewResponse, error) { // Get the deploy targets set on the deployment from the piped plugin config. + if len(request.GetDeployTargets()) == 0 { + return nil, status.Errorf(codes.InvalidArgument, "no deploy targets provided") + } + deployTargets := make([]*DeployTarget[DeployTargetConfig], 0, len(request.GetDeployTargets())) for _, name := range request.GetDeployTargets() { dt, ok := s.deployTargets[name] diff --git a/pkg/plugin/sdk/planpreview_test.go b/pkg/plugin/sdk/planpreview_test.go index 843707395a..e16fbae59d 100644 --- a/pkg/plugin/sdk/planpreview_test.go +++ b/pkg/plugin/sdk/planpreview_test.go @@ -150,7 +150,7 @@ spec: {} mockResp: &GetPlanPreviewResponse{}, err: errors.New("some error"), expectErr: true, - expectedStatus: codes.Internal, + expectedStatus: codes.InvalidArgument, }, } diff --git a/pkg/plugin/sdk/plugin.go b/pkg/plugin/sdk/plugin.go index 3bc1a191ab..67e9b64b86 100644 --- a/pkg/plugin/sdk/plugin.go +++ b/pkg/plugin/sdk/plugin.go @@ -340,6 +340,13 @@ func (p *Plugin[Config, DeployTargetConfig, ApplicationConfigSpec]) run(ctx cont } } + if len(commonFields.deployTargets) == 0 { + if p.deploymentPlugin != nil || p.livestatePlugin != nil || p.planPreviewPlugin != nil { + logger.Error("plugin requires at least one deploy target to be configured", zap.String("name", cfg.Name)) + return fmt.Errorf("plugin requires at least one deploy target to be configured") + } + } + client := &Client{ base: commonFields.client, pluginName: commonFields.name,