Description
When the modsecurity plugin allows a request (status < 400), the response headers set by the backend application are not forwarded to the client. Only the response body and status code are preserved.
Reproduction
Setup:
- Traefik v3 with
traefik-modsecurity-plugin v1.3.0
- ModSecurity sidecar (OWASP CRS 4.23.0, Apache)
- Backend: PHP application that returns CORS headers (
Access-Control-Allow-Origin, etc.)
Steps:
- Call the backend directly (bypassing Traefik):
kubectl exec deploy/portal-api-php -- curl -s -D- -o /dev/null \
-H "Origin: https://app.example.com" \
"http://localhost/api/portal/services/2000"
Result: ✅ CORS headers present
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: X-Requested-With, Content-Type, Accept, Origin, Authorization
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
- Call through Traefik without the modsecurity plugin (IngressRoute with no middlewares):
curl -sk -D- -o /dev/null \
-H "Origin: https://app.example.com" \
"https://app.example.com/api/portal/services/2000"
Result: ✅ CORS headers present
HTTP/2 200
access-control-allow-origin: *
access-control-allow-headers: X-Requested-With, Content-Type, Accept, Origin, Authorization
access-control-allow-methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
- Call through Traefik with the modsecurity plugin middleware:
curl -sk -D- -o /dev/null \
-H "Origin: https://app.example.com" \
"https://app.example.com/api/portal/services/2000"
Result: ❌ CORS headers missing
Analysis
In modsecurity.go, when modsecurity allows the request (status < 400), the plugin calls:
a.next.ServeHTTP(rw, req)
This passes the original ResponseWriter to the next handler, so response headers from the backend should be preserved. However, they are not.
A possible cause is the interaction between http.MaxBytesReader and Traefik's ResponseWriter:
body, err := ioutil.ReadAll(http.MaxBytesReader(rw, req.Body, a.maxBodySize))
http.MaxBytesReader stores a reference to rw. In Go 1.20+, this wrapper can interact with the ResponseWriter's internal state (via the requestTooLarge() interface), which may interfere with how Traefik's ResponseWriter wrapper flushes response headers.
Another possible cause: defer resp.Body.Close() on the modsecurity sidecar response runs after a.next.ServeHTTP(rw, req) returns, which could interfere with the final response flush in streaming scenarios.
Expected behavior
Response headers from the backend should be forwarded to the client when modsecurity allows the request, exactly as if the plugin were not in the middleware chain.
Environment
- Traefik v3 (chart v39.0.5)
- Plugin v1.3.0
- ModSecurity sidecar:
owasp/modsecurity-crs:4.23.0-apache-alpine-202602050102
- Go (Traefik binary)
Description
When the modsecurity plugin allows a request (status < 400), the response headers set by the backend application are not forwarded to the client. Only the response body and status code are preserved.
Reproduction
Setup:
traefik-modsecurity-pluginv1.3.0Access-Control-Allow-Origin, etc.)Steps:
Result: ✅ CORS headers present
Result: ✅ CORS headers present
Result: ❌ CORS headers missing
Analysis
In
modsecurity.go, when modsecurity allows the request (status < 400), the plugin calls:This passes the original
ResponseWriterto the next handler, so response headers from the backend should be preserved. However, they are not.A possible cause is the interaction between
http.MaxBytesReaderand Traefik'sResponseWriter:http.MaxBytesReaderstores a reference torw. In Go 1.20+, this wrapper can interact with the ResponseWriter's internal state (via therequestTooLarge()interface), which may interfere with how Traefik's ResponseWriter wrapper flushes response headers.Another possible cause:
defer resp.Body.Close()on the modsecurity sidecar response runs aftera.next.ServeHTTP(rw, req)returns, which could interfere with the final response flush in streaming scenarios.Expected behavior
Response headers from the backend should be forwarded to the client when modsecurity allows the request, exactly as if the plugin were not in the middleware chain.
Environment
owasp/modsecurity-crs:4.23.0-apache-alpine-202602050102