From e3cc7c1c9cbc29345363bbfca75de34754248076 Mon Sep 17 00:00:00 2001 From: cavidelizade Date: Fri, 17 Jul 2026 00:09:14 +0400 Subject: [PATCH] fix(api): cap the GitHub webhook request body size The webhook receiver read the entire request body with io.ReadAll and no size limit, before checking the signature, on a public unauthenticated route. A single large POST (or a few concurrent ones) could exhaust process memory. Wrap the body in http.MaxBytesReader at GitHub's 25 MiB payload cap and return 413 when it's exceeded. Closes #329 Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/api/internal/handler/integration.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/api/internal/handler/integration.go b/apps/api/internal/handler/integration.go index 1bc6445b..11af41ba 100644 --- a/apps/api/internal/handler/integration.go +++ b/apps/api/internal/handler/integration.go @@ -501,8 +501,18 @@ func (h *IntegrationHandler) GitHubIssueSummary(c *gin.Context) { // auth — authentication is the HMAC signature in X-Hub-Signature-256. // POST /webhooks/github func (h *IntegrationHandler) GitHubWebhook(c *gin.Context) { + // This route is public (auth is the HMAC signature). Cap the body before + // reading it so an unauthenticated client can't exhaust memory with a huge + // payload. GitHub caps webhook deliveries at 25 MiB. + const maxWebhookBody = 25 << 20 + c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxWebhookBody) body, err := io.ReadAll(c.Request.Body) if err != nil { + var tooLarge *http.MaxBytesError + if errors.As(err, &tooLarge) { + c.JSON(http.StatusRequestEntityTooLarge, gin.H{"error": "Payload too large"}) + return + } c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to read body"}) return }