fix(line): limit webhook request body size to prevent DoS
Add io.LimitReader with 1 MB cap on the LINE webhook handler to prevent unauthenticated memory exhaustion via oversized POST requests. Follows the same pattern used in the WeCom channel (io.LimitReader). Requests exceeding the limit are rejected with 413 Request Entity Too Large. Fixes #1407 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b5bd434ddb
commit
58595330f4
2 changed files with 61 additions and 1 deletions
|
|
@ -166,7 +166,10 @@ func (c *LINEChannel) webhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
// Limit request body to prevent memory exhaustion (DoS).
|
||||
// LINE webhook payloads are typically a few KB; 1 MB is generous.
|
||||
const maxWebhookBodySize = 1 << 20 // 1 MB
|
||||
body, err := io.ReadAll(io.LimitReader(r.Body, maxWebhookBodySize+1))
|
||||
if err != nil {
|
||||
logger.ErrorCF("line", "Failed to read request body", map[string]any{
|
||||
"error": err.Error(),
|
||||
|
|
@ -174,6 +177,11 @@ func (c *LINEChannel) webhookHandler(w http.ResponseWriter, r *http.Request) {
|
|||
http.Error(w, "Bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if int64(len(body)) > maxWebhookBodySize {
|
||||
logger.WarnC("line", "Webhook request body too large, rejected")
|
||||
http.Error(w, "Request entity too large", http.StatusRequestEntityTooLarge)
|
||||
return
|
||||
}
|
||||
|
||||
signature := r.Header.Get("X-Line-Signature")
|
||||
if !c.verifySignature(body, signature) {
|
||||
|
|
|
|||
52
pkg/channels/line/line_test.go
Normal file
52
pkg/channels/line/line_test.go
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
package line
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWebhookRejectsOversizedBody(t *testing.T) {
|
||||
ch := &LINEChannel{}
|
||||
|
||||
// Create a body larger than maxWebhookBodySize (1 MB)
|
||||
oversized := bytes.Repeat([]byte("A"), (1<<20)+1)
|
||||
req := httptest.NewRequest(http.MethodPost, "/webhook", bytes.NewReader(oversized))
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
ch.webhookHandler(rec, req)
|
||||
|
||||
if rec.Code != http.StatusRequestEntityTooLarge {
|
||||
t.Errorf("expected status %d, got %d", http.StatusRequestEntityTooLarge, rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookRejectsNonPostMethod(t *testing.T) {
|
||||
ch := &LINEChannel{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/webhook", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
ch.webhookHandler(rec, req)
|
||||
|
||||
if rec.Code != http.StatusMethodNotAllowed {
|
||||
t.Errorf("expected status %d, got %d", http.StatusMethodNotAllowed, rec.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookRejectsInvalidSignature(t *testing.T) {
|
||||
ch := &LINEChannel{}
|
||||
|
||||
body := `{"events":[]}`
|
||||
req := httptest.NewRequest(http.MethodPost, "/webhook", strings.NewReader(body))
|
||||
req.Header.Set("X-Line-Signature", "invalidsignature")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
ch.webhookHandler(rec, req)
|
||||
|
||||
if rec.Code != http.StatusForbidden {
|
||||
t.Errorf("expected status %d, got %d", http.StatusForbidden, rec.Code)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue