pipe-cd / pipe-cd/pipecd

ListDeployments panics when statuses or kinds contain only empty values

Open
#7,098 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
1.4k
Forks
364
Avg merge
1d 4h
Merged PRs (30d)
84

Description

What happened:

APIService.ListDeployments panics with index out of range [0] with length 0 when the request has statuses (or kinds) that are not empty as a list, but every value inside is an empty string.

In pkg/app/server/grpcapi/api.go the loop skips empty values, but statuses[0] is read after it without checking that anything was actually added:

if len(req.Statuses) > 0 {
    statuses := []model.DeploymentStatus{}
    for _, s := range req.Statuses {
        if s != "" {                       // empty values are skipped here
            ...
            statuses = append(statuses, model.DeploymentStatus(depstatus))
        }
    }

    filters = append(filters, datastore.ListFilter{
        Field:    "Status",
        Operator: datastore.OperatorEqual,
        Value:    statuses[0],             // <- panics when the slice stayed empty
    })
}

The same pattern is in the kinds block right below it.

I think this is worse than a normal error because the API server is started without any recovery interceptor (cmd/pipecd/server.go only adds log, api key auth, request validation and prometheus), so the panic is not caught and it stops the whole control plane process, not only that one request.

What you expected to happen:

If all given values are empty, no status/kind filter should be added and the request should be handled normally, like when no filter is given at all.

How to reproduce it:

The shortest way is with pipectl:

pipectl deployment list --status=,

--status=, looks strange but it is what a user gets from an empty item in a comma separated list. pflag parses the value as CSV, so , becomes two empty strings.

I did not want to guess how pflag behaves, so I checked it with a small test that declares the flag the same way pkg/app/pipectl/cmd/deployment/list.go does:

var statuses []string
fs := pflag.NewFlagSet("probe", pflag.ContinueOnError)
fs.StringSliceVar(&statuses, "status", nil, "")
fs.Parse([]string{arg})

Result:

--status=          -> []               len=0                 ok
--status=,         -> ["" ""]          len=2, none usable    panics
--status=""        -> [""]             len=1, none usable    panics
--status=,,        -> ["" "" ""]       len=3, none usable    panics
--status=SUCCESS,  -> ["SUCCESS" ""]   len=2, one usable     ok

pipectl lets those empty values through on purpose. In pkg/app/pipectl/cmd/deployment/list.go the validation skips them:

for _, status := range c.statuses {
    if status != "" {
        if _, ok := model.DeploymentStatus_value[status]; !ok {
            return errors.Errorf("%s is invalid deployment status", status)
        }
    }
}

so they pass client side validation and reach the server as they are.

Any other gRPC client can also send statuses: [""] directly. The field has no validate rule in service.proto, so WithRequestValidationUnaryInterceptor does not reject it either.

To see the panic itself I called the handler directly:

ctx := rpcauth.ContextWithAPIKey(context.Background(), &model.APIKey{
    ProjectId: "p", Role: model.APIKey_READ_ONLY,
})
api := &API{deploymentStore: someStore, logger: zap.NewNop()}
api.ListDeployments(ctx, &apiservice.ListDeploymentsRequest{
    Statuses: []string{""},
    Limit:    10,
})
panic: runtime error: index out of range [0] with length 0

github.com/pipe-cd/pipecd/pkg/app/server/grpcapi.(*API).ListDeployments(...)
	/pkg/app/server/grpcapi/api.go:553

It never reaches the datastore, the panic happens while the filters are being built.

Environment:

  • piped version: not related
  • control-plane version: master at commit 56fb80e7
  • Others: pipectl on the same commit

I have a small fix and a regression test for this in #7099.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in pkg/app/server/grpcapi/api.go at API.ListDeployments and review the status and kind filter construction. Reproduce with pipectl deployment list --status=, or the direct handler call, then add or run a regression test covering only empty values. Done means the request reaches normal handling without a panic and no empty filter is added.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.