crowdsecurity/crowdsec · warning
unable to read body: %w
Error message
unable to read body: %w
What it means
readRequestBody reads the bouncer-forwarded body with a size-limited reader and tolerates ErrUnexpectedEOF and read-timeout errors (partial bodies on empty POSTs / deadline expiry are accepted). This error is returned for any other body-read failure, meaning the connection errored in a way the WAF cannot safely continue from.
Source
Thrown at pkg/appsec/request.go:335
if maxSize <= 0 {
maxSize = DefaultMaxBodySize
}
action := bodySettings.Action
if action == "" {
action = BodySizeActionDrop
}
// Always read from the actual stream — never trust Content-Length.
// Read up to maxSize+1 bytes so we can detect whether the body exceeds the limit.
body, err = io.ReadAll(io.LimitReader(r.Body, maxSize+1))
var netErr net.Error
hasTimedout := err != nil && errors.As(err, &netErr) && netErr.Timeout()
// ErrUnexpectedEOF can occur on POST requests without a body — accept what was read.
// A net.Error timeout means the read deadline fired; keep what we got and move on.
// Bouncers are semi-trusted; misbehaving ones would otherwise stall the WAF for seconds.
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) && !hasTimedout {
return nil, false, false, fmt.Errorf("unable to read body: %w", err)
}
if int64(len(body)) > maxSize {
// Drain remaining bytes so the client doesn't time out waiting for us to finish reading.
// The LimitReader stopped at maxSize+1, so r.Body may still have unread bytes.
_, _ = io.Copy(io.Discard, r.Body)
switch action {
case BodySizeActionDrop:
logger.Warnf("request body exceeds limit %d bytes, will drop request", maxSize)
body = nil
exceeded = true
case BodySizeActionAllow:
logger.Warnf("request body exceeds limit %d bytes, skipping body inspection", maxSize)
body = nil
case BodySizeActionPartial:
logger.Warnf("request body exceeds limit %d bytes, truncating", maxSize)
body = body[:maxSize]View on GitHub (pinned to 909b515798)
Solutions
- Check connectivity and timeouts between the bouncer/proxy and crowdsec appsec component
- Look at bouncer logs for aborted requests or mismatches in Content-Length
- Increase bouncer-side timeouts if large bodies are regularly cut off
- If persistent on one path, capture traffic to identify who resets the connection
Defensive patterns
Strategy: try-catch
Validate before calling
null
Try / catch
pr, _, _, err := NewParsedRequestFromRequest(r, logger)
if err != nil {
log.Debugf("unreadable request body: %v", err)
http.Error(w, "bad request", http.StatusBadRequest)
return
} Prevention
- Keep bouncer timeouts generous enough for the largest expected bodies
- Monitor connection resets between bouncer and appsec
- Upgrade bouncers to keep body-forwarding behavior in sync
When it happens
Trigger: NewParsedRequestFromRequest → readRequestBody when r.Body read fails with an error that is neither io.ErrUnexpectedEOF nor a net.Error timeout — e.g. connection reset by the bouncer mid-body, TLS errors, or aborted connections.
Common situations: Bouncer or reverse proxy closing the connection while the body is still being read, client aborts, keep-alive races, or network interruptions between bouncer and crowdsec. Usually a symptom of upstream flakiness rather than a crowdsec bug.
Related errors
- failed to read body: %w
- cannot read line in response: %w
- error while reading tail response: %w
- unable to read file %s : %w
- appsec datasource requires a hub. this is a bug, please repo
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/c5edfa4048215619.
Report an issue: GitHub.