From e2129101439bf30671883ad291a2b169f4a567f6 Mon Sep 17 00:00:00 2001 From: clawbot Date: Sun, 8 Feb 2026 12:02:06 -0800 Subject: [PATCH] fix: limit webhook request body size to 1MB to prevent DoS (closes #1) --- internal/handlers/handlers_test.go | 41 ++++++++++++++++++++++++++++++ internal/handlers/webhook.go | 7 +++-- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go index adec558..58266fb 100644 --- a/internal/handlers/handlers_test.go +++ b/internal/handlers/handlers_test.go @@ -426,6 +426,47 @@ func addChiURLParams( ) } +func TestHandleWebhookRejectsOversizedBody(t *testing.T) { + t.Parallel() + + testCtx := setupTestHandlers(t) + + // Create an app first + createdApp, createErr := testCtx.appSvc.CreateApp( + context.Background(), + app.CreateAppInput{ + Name: "oversize-test-app", + RepoURL: "git@example.com:user/repo.git", + Branch: "main", + }, + ) + require.NoError(t, createErr) + + // Create a body larger than 1MB - it should be silently truncated + // and the webhook should still process (or fail gracefully on parse) + largePayload := strings.Repeat("x", 2*1024*1024) // 2MB + request := httptest.NewRequest( + http.MethodPost, + "/webhook/"+createdApp.WebhookSecret, + strings.NewReader(largePayload), + ) + request = addChiURLParams( + request, + map[string]string{"secret": createdApp.WebhookSecret}, + ) + request.Header.Set("Content-Type", "application/json") + request.Header.Set("X-Gitea-Event", "push") + + recorder := httptest.NewRecorder() + + handler := testCtx.handlers.HandleWebhook() + handler.ServeHTTP(recorder, request) + + // Should still return OK (payload is truncated and fails JSON parse, + // but webhook service handles invalid JSON gracefully) + assert.Equal(t, http.StatusOK, recorder.Code) +} + func TestHandleWebhookReturns404ForUnknownSecret(t *testing.T) { t.Parallel() diff --git a/internal/handlers/webhook.go b/internal/handlers/webhook.go index e1e0ba9..03dae41 100644 --- a/internal/handlers/webhook.go +++ b/internal/handlers/webhook.go @@ -9,6 +9,9 @@ import ( "git.eeqj.de/sneak/upaas/internal/models" ) +// maxWebhookBodySize is the maximum allowed size of a webhook request body (1MB). +const maxWebhookBodySize = 1 << 20 + // HandleWebhook handles incoming Gitea webhooks. func (h *Handlers) HandleWebhook() http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { @@ -38,8 +41,8 @@ func (h *Handlers) HandleWebhook() http.HandlerFunc { return } - // Read request body - body, readErr := io.ReadAll(request.Body) + // Read request body with size limit to prevent memory exhaustion + body, readErr := io.ReadAll(io.LimitReader(request.Body, maxWebhookBodySize)) if readErr != nil { h.log.Error("failed to read webhook body", "error", readErr) http.Error(writer, "Bad Request", http.StatusBadRequest) -- 2.45.2