diff --git a/internal/observabilityproxy/logsparams.go b/internal/observabilityproxy/logsparams.go index 653f1c194..c9fad47db 100644 --- a/internal/observabilityproxy/logsparams.go +++ b/internal/observabilityproxy/logsparams.go @@ -264,9 +264,12 @@ var durationUnits = map[string]time.Duration{ // parseDuration accepts the LogsQL duration forms (1h30m, 1d, 2w) and bare // seconds; Go's parser knows neither d nor w. func parseDuration(s string) (time.Duration, error) { - if f, err := strconv.ParseFloat(s, 64); err == nil { - if f < 0 { - return 0, fmt.Errorf("negative duration %q", s) + // Bare seconds only as digits with an optional fraction: ParseFloat + // would also take signs, exponents, Inf and NaN. + if whole, frac, ok := strings.Cut(s, "."); isDigits(whole) && (!ok || isDigits(frac)) { + f, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0, fmt.Errorf("invalid duration %q", s) } return time.Duration(f * float64(time.Second)), nil } diff --git a/internal/observabilityproxy/logsparams_test.go b/internal/observabilityproxy/logsparams_test.go index 8e6fb107d..abfbc3483 100644 --- a/internal/observabilityproxy/logsparams_test.go +++ b/internal/observabilityproxy/logsparams_test.go @@ -128,6 +128,14 @@ func TestBuildLogsParamsRejects(t *testing.T) { {"bad start", "query", url.Values{"start": {"yesterday"}}, http.StatusBadRequest}, {"millisecond timestamp", "query", url.Values{"start": {"1793786400000"}}, http.StatusBadRequest}, {"year-like number", "query", url.Values{"start": {"2026"}}, http.StatusBadRequest}, + {"exponent time", "query", url.Values{"start": {"1.7937864e9"}}, http.StatusBadRequest}, + {"signed time", "query", url.Values{"start": {"+1793786400"}}, http.StatusBadRequest}, + {"exponent duration", "query", url.Values{"start": {"3e2"}}, http.StatusBadRequest}, + {"trailing dot", "query", url.Values{"start": {"300."}}, http.StatusBadRequest}, + {"infinite time", "query", url.Values{"end": {"Inf"}}, http.StatusBadRequest}, + {"NaN time", "query", url.Values{"start": {"NaN"}}, http.StatusBadRequest}, + {"NaN step", "hits", url.Values{"step": {"NaN"}}, http.StatusBadRequest}, + {"NaN start_offset", "tail", url.Values{"start_offset": {"NaN"}}, http.StatusBadRequest}, {"bad limit", "query", url.Values{"limit": {"-1"}}, http.StatusBadRequest}, {"zero limit", "query", url.Values{"limit": {"0"}}, http.StatusBadRequest}, {"bad step", "hits", url.Values{"step": {"0s"}}, http.StatusBadRequest},