Limit webhook request body size to 1MB to prevent DoS (closes #1) #6
@ -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) {
|
func TestHandleWebhookReturns404ForUnknownSecret(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,9 @@ import (
|
|||||||
"git.eeqj.de/sneak/upaas/internal/models"
|
"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.
|
// HandleWebhook handles incoming Gitea webhooks.
|
||||||
func (h *Handlers) HandleWebhook() http.HandlerFunc {
|
func (h *Handlers) HandleWebhook() http.HandlerFunc {
|
||||||
return func(writer http.ResponseWriter, request *http.Request) {
|
return func(writer http.ResponseWriter, request *http.Request) {
|
||||||
@ -38,8 +41,8 @@ func (h *Handlers) HandleWebhook() http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read request body
|
// Read request body with size limit to prevent memory exhaustion
|
||||||
body, readErr := io.ReadAll(request.Body)
|
body, readErr := io.ReadAll(io.LimitReader(request.Body, maxWebhookBodySize))
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
h.log.Error("failed to read webhook body", "error", readErr)
|
h.log.Error("failed to read webhook body", "error", readErr)
|
||||||
http.Error(writer, "Bad Request", http.StatusBadRequest)
|
http.Error(writer, "Bad Request", http.StatusBadRequest)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user