fix(observability-proxy): accept bare seconds only as plain digits

Bare seconds were parsed with ParseFloat, which also takes signs,
exponents, Inf and NaN. Such a start fell through to the relative
duration form and silently moved the window, so 1.7937864e9 meant
1970 rather than an error, and NaN converted differently per CPU
architecture. Only digits with an optional fraction are taken now;
everything else is a 400.

Assisted-by: LLM
Signed-off-by: ZverGuy <maximbel2003@gmail.com>
This commit is contained in:
ZverGuy committed 2026-11-09 15:48:57 +07:00
1 parent 8eef2fa5d2
commit 4e8071aa2b
2 files changed
+14 -3

No files matched your search

+6 -3
View File
@@ -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
}
@@ -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},