Skip to content

Commit 9d4295f

Browse files
committed
fix: protect against gzip bombs, add test
1 parent f47b084 commit 9d4295f

File tree

2 files changed

+80
-2
lines changed

2 files changed

+80
-2
lines changed

router/core/graph_server.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,12 +237,13 @@ func newGraphServer(ctx context.Context, r *Router, routerConfig *nodev1.RouterC
237237
)
238238
})))
239239

240-
// Request traffic shaping related middlewares
241-
httpRouter.Use(rmiddleware.RequestSize(int64(s.routerTrafficConfig.MaxRequestBodyBytes)))
242240
if s.routerTrafficConfig.DecompressionEnabled {
243241
httpRouter.Use(rmiddleware.HandleCompression(s.logger))
244242
}
245243

244+
// Request traffic shaping related middlewares, happens after decompression to prevent unbounded decompression attacks
245+
httpRouter.Use(rmiddleware.RequestSize(int64(s.routerTrafficConfig.MaxRequestBodyBytes)))
246+
246247
httpRouter.Use(middleware.RequestID)
247248
httpRouter.Use(middleware.RealIP)
248249
if s.corsOptions.Enabled {
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package middleware
2+
3+
import (
4+
"bytes"
5+
"compress/gzip"
6+
"io"
7+
"net/http"
8+
"net/http/httptest"
9+
"strings"
10+
"testing"
11+
12+
"github.com/go-chi/chi/v5"
13+
"github.com/stretchr/testify/require"
14+
"go.uber.org/zap"
15+
)
16+
17+
func TestRequestSizeAndCompression(t *testing.T) {
18+
// Define maximum allowed request size
19+
const maxRequestSize = 1024 // Example: 1 KB
20+
21+
// Chain the compression and request_size middlewares
22+
r := chi.NewMux()
23+
r.Use(HandleCompression(zap.NewNop()))
24+
r.Use(RequestSize(maxRequestSize))
25+
26+
t.Run("request size limiter should not allow requests exceeding allowed maximum request size", func(t *testing.T) {
27+
// Recorder to capture the response
28+
recorder := httptest.NewRecorder()
29+
30+
// Write a large payload that exceeds the maxRequestSize after decompression
31+
largePayload := strings.Repeat("A", maxRequestSize*10) // 10x the max size
32+
33+
// Create the request with the gzip bomb payload
34+
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(largePayload))
35+
36+
r.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
37+
_, err := io.Copy(io.Discard, r.Body)
38+
require.ErrorContains(t, err, "http: request body too large")
39+
w.WriteHeader(http.StatusRequestEntityTooLarge)
40+
}))
41+
42+
r.ServeHTTP(recorder, req)
43+
44+
// Assert that the response status is 413 Payload Too Large
45+
require.Equal(t, http.StatusRequestEntityTooLarge, recorder.Code)
46+
})
47+
48+
t.Run("request size limiter should reject gzip bomb exceeding allowed maximum request size", func(t *testing.T) {
49+
// Recorder to capture the response
50+
recorder := httptest.NewRecorder()
51+
52+
// Create a gzip bomb payload
53+
var buf bytes.Buffer
54+
w := gzip.NewWriter(&buf)
55+
56+
// Write a large payload that exceeds the maxRequestSize after decompression
57+
largePayload := strings.Repeat("A", maxRequestSize*10) // 10x the max size
58+
_, err := w.Write([]byte(largePayload))
59+
require.NoError(t, err)
60+
require.NoError(t, w.Close())
61+
62+
// Create the request with the gzip bomb payload
63+
req := httptest.NewRequest(http.MethodPost, "/", &buf)
64+
req.Header.Set("Content-Encoding", "gzip")
65+
66+
r.HandleFunc("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
67+
_, err := io.Copy(io.Discard, r.Body)
68+
require.ErrorContains(t, err, "http: request body too large")
69+
w.WriteHeader(http.StatusRequestEntityTooLarge)
70+
}))
71+
72+
r.ServeHTTP(recorder, req)
73+
74+
// Assert that the response status is 413 Payload Too Large
75+
require.Equal(t, http.StatusRequestEntityTooLarge, recorder.Code)
76+
})
77+
}

0 commit comments

Comments
 (0)