diff --git a/workspace-server/internal/channels/discord.go b/workspace-server/internal/channels/discord.go index e640e20f..965313f8 100644 --- a/workspace-server/internal/channels/discord.go +++ b/workspace-server/internal/channels/discord.go @@ -106,7 +106,11 @@ func (d *DiscordAdapter) SendMessage(ctx context.Context, config map[string]inte // Returns nil, nil for PING payloads — the handler layer must respond with `{"type":1}` to pass // Discord's endpoint verification. Returns an InboundMessage for APPLICATION_COMMAND payloads. func (d *DiscordAdapter) ParseWebhook(c *gin.Context, _ map[string]interface{}) (*InboundMessage, error) { - body, err := io.ReadAll(c.Request.Body) + // Cap incoming webhook bodies at 1 MiB. Discord's Interactions API + // payloads are well under 10 KiB in practice; the cap is a DoS + // guard, not a functional limit. + const maxDiscordWebhook = 1 << 20 + body, err := io.ReadAll(io.LimitReader(c.Request.Body, maxDiscordWebhook)) if err != nil { return nil, fmt.Errorf("discord: read body: %w", err) } diff --git a/workspace-server/internal/handlers/config.go b/workspace-server/internal/handlers/config.go index 9214be5c..fa83b98b 100644 --- a/workspace-server/internal/handlers/config.go +++ b/workspace-server/internal/handlers/config.go @@ -42,9 +42,14 @@ func (h *ConfigHandler) Get(c *gin.Context) { func (h *ConfigHandler) Patch(c *gin.Context) { workspaceID := c.Param("id") + // 256 KiB cap: Postgres jsonb comfortably handles this and real + // configs are <10 KiB. The cap blocks naive memory-exhaustion DoS + // — a caller streaming a gigabyte of JSON would OOM the instance. + const maxConfigBody = 256 << 10 + c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxConfigBody) body, err := io.ReadAll(c.Request.Body) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "failed to read body"}) + c.JSON(http.StatusRequestEntityTooLarge, gin.H{"error": "body too large or unreadable"}) return }