diff --git a/server/plugin/webhook.go b/server/plugin/webhook.go index 0608afd18..7271acd21 100644 --- a/server/plugin/webhook.go +++ b/server/plugin/webhook.go @@ -9,6 +9,7 @@ import ( "crypto/sha1" //nolint:gosec // GitHub webhooks are signed using sha1 https://developer.github.com/webhooks/. "encoding/hex" "encoding/json" + "errors" "html" "io" "net/http" @@ -199,11 +200,20 @@ func (wb *WebhookBroker) Close() { } } +const maxWebhookPayloadSize = 25 * 1024 * 1024 // 25 MB, matching GitHub's documented maximum + func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) { p.client.Log.Info("Webhook event received") config := p.getConfiguration() + + r.Body = http.MaxBytesReader(w, r.Body, maxWebhookPayloadSize) body, err := io.ReadAll(r.Body) if err != nil { + var maxBytesErr *http.MaxBytesError + if errors.As(err, &maxBytesErr) { + http.Error(w, "Request body too large", http.StatusRequestEntityTooLarge) + return + } http.Error(w, "Bad request body", http.StatusBadRequest) return } diff --git a/server/plugin/webhook_test.go b/server/plugin/webhook_test.go index 9faec3b01..27b1b3856 100644 --- a/server/plugin/webhook_test.go +++ b/server/plugin/webhook_test.go @@ -30,6 +30,52 @@ const ( gitHubOrginization = "test-org" ) +func TestHandleWebhookBodySizeLimit(t *testing.T) { + t.Run("rejects oversized request body", func(t *testing.T) { + _, mockAPI, _, _, _ := GetTestSetup(t) + p := NewPlugin() + p.initializeAPI() + p.SetAPI(mockAPI) + p.client = pluginapi.NewClient(mockAPI, p.Driver) + p.setConfiguration(&Configuration{ + WebhookSecret: webhookSecret, + }) + + mockAPI.On("LogInfo", "Webhook event received") + + oversizedBody := strings.NewReader(strings.Repeat("x", 26*1024*1024)) + req := httptest.NewRequest(http.MethodPost, "/webhook", oversizedBody) + req.Header.Set("X-Hub-Signature", "sha1=invalid") + w := httptest.NewRecorder() + + p.handleWebhook(w, req) + + assert.Equal(t, http.StatusRequestEntityTooLarge, w.Code) + }) + + t.Run("accepts normal sized request body", func(t *testing.T) { + _, mockAPI, _, _, _ := GetTestSetup(t) + p := NewPlugin() + p.initializeAPI() + p.SetAPI(mockAPI) + p.client = pluginapi.NewClient(mockAPI, p.Driver) + p.setConfiguration(&Configuration{ + WebhookSecret: webhookSecret, + }) + + mockAPI.On("LogInfo", "Webhook event received") + + body := `{"zen": "test"}` + req := httptest.NewRequest(http.MethodPost, "/webhook", strings.NewReader(body)) + req.Header.Set("X-Hub-Signature", "sha1=invalid") + w := httptest.NewRecorder() + + p.handleWebhook(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) + }) +} + func TestPostPushEvent(t *testing.T) { tests := []struct { name string