From 300de448534c77e166be7b464c845850846f0933 Mon Sep 17 00:00:00 2001 From: user Date: Sun, 15 Feb 2026 21:50:00 -0800 Subject: [PATCH] fix: validate and clamp container log tail parameter (closes #24) - Add sanitizeTail() helper that validates tail is numeric and positive - Clamp values to max 500 - Default to 500 when empty, non-numeric, zero, or negative - Add comprehensive test cases --- internal/handlers/app.go | 27 +++++++++++++--- internal/handlers/tail_validation_test.go | 38 +++++++++++++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 internal/handlers/tail_validation_test.go diff --git a/internal/handlers/app.go b/internal/handlers/app.go index b98eedc..f60435c 100644 --- a/internal/handlers/app.go +++ b/internal/handlers/app.go @@ -373,6 +373,28 @@ func (h *Handlers) HandleAppDeployments() http.HandlerFunc { // defaultLogTail is the default number of log lines to fetch. const defaultLogTail = "500" +// maxLogTail is the maximum allowed value for the tail parameter. +const maxLogTail = 500 + +// sanitizeTail validates and clamps the tail query parameter. +// It returns a numeric string clamped to maxLogTail, or the default if invalid. +func sanitizeTail(raw string) string { + if raw == "" { + return defaultLogTail + } + + n, err := strconv.Atoi(raw) + if err != nil || n < 1 { + return defaultLogTail + } + + if n > maxLogTail { + n = maxLogTail + } + + return strconv.Itoa(n) +} + // HandleAppLogs returns the container logs handler. func (h *Handlers) HandleAppLogs() http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { @@ -394,10 +416,7 @@ func (h *Handlers) HandleAppLogs() http.HandlerFunc { return } - tail := request.URL.Query().Get("tail") - if tail == "" { - tail = defaultLogTail - } + tail := sanitizeTail(request.URL.Query().Get("tail")) logs, logsErr := h.docker.ContainerLogs( request.Context(), diff --git a/internal/handlers/tail_validation_test.go b/internal/handlers/tail_validation_test.go new file mode 100644 index 0000000..0ec0d7a --- /dev/null +++ b/internal/handlers/tail_validation_test.go @@ -0,0 +1,38 @@ +package handlers + +import ( + "testing" +) + +func TestSanitizeTail(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + expected string + }{ + {"empty uses default", "", defaultLogTail}, + {"valid small number", "50", "50"}, + {"valid max boundary", "500", "500"}, + {"exceeds max clamped", "501", "500"}, + {"very large clamped", "999999", "500"}, + {"non-numeric uses default", "abc", defaultLogTail}, + {"all keyword uses default", "all", defaultLogTail}, + {"negative uses default", "-1", defaultLogTail}, + {"zero uses default", "0", defaultLogTail}, + {"float uses default", "1.5", defaultLogTail}, + {"one is valid", "1", "1"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + got := sanitizeTail(tc.input) + if got != tc.expected { + t.Errorf("sanitizeTail(%q) = %q, want %q", tc.input, got, tc.expected) + } + }) + } +}