From 5b2661956506daa84148611db52eb32741739704 Mon Sep 17 00:00:00 2001 From: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> Date: Thu, 4 Dec 2025 10:57:50 +0100 Subject: [PATCH] web/api: Add maximum limit validation to TSDB status endpoint Add a maximum limit of 10,000 to the TSDB status endpoint to prevent resource exhaustion from excessively large limit values, as we preallocate []Stat for up to the limit: `make([]Stat, 0, length)`. Note that the endpoint acquires a cardinality mutex during stats calculation, so this can not be run in parallel. Signed-off-by: Julien Pivotto <291750+roidelapluie@users.noreply.github.com> --- docs/querying/api.md | 2 +- web/api/v1/api.go | 4 ++++ web/api/v1/api_test.go | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/querying/api.md b/docs/querying/api.md index b377c6174e..4804443343 100644 --- a/docs/querying/api.md +++ b/docs/querying/api.md @@ -1346,7 +1346,7 @@ GET /api/v1/status/tsdb ``` URL query parameters: -- `limit=`: Limit the number of returned items to a given number for each set of statistics. By default, 10 items are returned. +- `limit=`: Limit the number of returned items to a given number for each set of statistics. By default, 10 items are returned. The maximum allowed limit is 10000. The `data` section of the query result consists of: diff --git a/web/api/v1/api.go b/web/api/v1/api.go index 86c0461087..fd3652f4e4 100644 --- a/web/api/v1/api.go +++ b/web/api/v1/api.go @@ -1837,12 +1837,16 @@ func (api *API) serveTSDBBlocks(*http.Request) apiFuncResult { } func (api *API) serveTSDBStatus(r *http.Request) apiFuncResult { + const maxTSDBLimit = 10000 limit := 10 if s := r.FormValue("limit"); s != "" { var err error if limit, err = strconv.Atoi(s); err != nil || limit < 1 { return apiFuncResult{nil, &apiError{errorBadData, errors.New("limit must be a positive number")}, nil, nil} } + if limit > maxTSDBLimit { + return apiFuncResult{nil, &apiError{errorBadData, fmt.Errorf("limit must not exceed %d", maxTSDBLimit)}, nil, nil} + } } s, err := api.db.Stats(labels.MetricName, limit) if err != nil { diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index 8e0adc0802..83e8618630 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -4465,6 +4465,18 @@ func TestTSDBStatus(t *testing.T) { values: map[string][]string{"limit": {"0"}}, errType: errorBadData, }, + { + db: tsdb, + endpoint: tsdbStatusAPI, + values: map[string][]string{"limit": {"10000"}}, + errType: errorNone, + }, + { + db: tsdb, + endpoint: tsdbStatusAPI, + values: map[string][]string{"limit": {"10001"}}, + errType: errorBadData, + }, } { t.Run(strconv.Itoa(i), func(t *testing.T) { api := &API{db: tc.db, gatherer: prometheus.DefaultGatherer}