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

  1. Check connectivity and timeouts between the bouncer/proxy and crowdsec appsec component
  2. Look at bouncer logs for aborted requests or mismatches in Content-Length
  3. Increase bouncer-side timeouts if large bodies are regularly cut off
  4. 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

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


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/c5edfa4048215619. Report an issue: GitHub.