Merge pull request 'fix: validate and clamp container log tail parameter (closes #24)' (#33) from clawbot/upaas:fix/validate-tail-parameter into main

Reviewed-on: #33
This commit is contained in:
Jeffrey Paul 2026-02-16 06:51:34 +01:00
commit e30a7568cf
2 changed files with 61 additions and 4 deletions

View File

@ -373,6 +373,28 @@ func (h *Handlers) HandleAppDeployments() http.HandlerFunc {
// defaultLogTail is the default number of log lines to fetch. // defaultLogTail is the default number of log lines to fetch.
const defaultLogTail = "500" 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. // HandleAppLogs returns the container logs handler.
func (h *Handlers) HandleAppLogs() http.HandlerFunc { func (h *Handlers) HandleAppLogs() http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) { return func(writer http.ResponseWriter, request *http.Request) {
@ -394,10 +416,7 @@ func (h *Handlers) HandleAppLogs() http.HandlerFunc {
return return
} }
tail := request.URL.Query().Get("tail") tail := sanitizeTail(request.URL.Query().Get("tail"))
if tail == "" {
tail = defaultLogTail
}
logs, logsErr := h.docker.ContainerLogs( logs, logsErr := h.docker.ContainerLogs(
request.Context(), request.Context(),

View File

@ -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)
}
})
}
}