fix(waf): HTTP/3 bodies were stamped complete when truncated (2026.10.5) #12

Merged
jknapp merged 2 commits from fix/waf-h3-body-stamp into main 2026-10-19 21:22:14 +00:00
Owner

Over HTTP/3, HAProxy 3.0's req.body_size equals the bytes buffered, because the H3 mux never sets htx->extra from Content-Length. As a result, any body cut at one buffer (~15 KB) was stamped X-WHP-WAF-Body: complete. Coraza then failed to parse the truncated JSON (200002), and WHP rule 991900020 returned a 403.

  • whp01, Oct 6-19: 73,795 H3 complete stamps and 0 partial. 7 legitimate requests were 403'd: 6 Divi sync-to-server saves of 265-417 KB and 1 Groundhogg preview of 18 KB.
  • Fix: take the advertised length from req.hdr_val(content-length). H1, H2 and H3 all reject a Content-Length that doesn't match the DATA received, so the header is a protocol-independent body length. No change on H1/H2.
  • Tests: scripts/test-waf-body-inspection.py passes 28/28. With the template reverted, 2 tests fail.
  • Lab: verified on haproxy 3.0.11 with QUIC. 18 KB, 265 KB and 417 KB bodies are stamped partial on H1, H2 and H3; bodies of 14 KB or less are stamped complete.

Deploy: rebuild the image, then recreate haproxy-manager per host, .148 first.

🤖 Generated with Claude Code

Over HTTP/3, HAProxy 3.0's `req.body_size` equals the bytes buffered, because the H3 mux never sets `htx->extra` from Content-Length. As a result, any body cut at one buffer (~15 KB) was stamped `X-WHP-WAF-Body: complete`. Coraza then failed to parse the truncated JSON (200002), and WHP rule 991900020 returned a 403. - **whp01, Oct 6-19:** 73,795 H3 `complete` stamps and 0 `partial`. 7 legitimate requests were 403'd: 6 Divi `sync-to-server` saves of 265-417 KB and 1 Groundhogg preview of 18 KB. - **Fix:** take the advertised length from `req.hdr_val(content-length)`. H1, H2 and H3 all reject a Content-Length that doesn't match the DATA received, so the header is a protocol-independent body length. No change on H1/H2. - **Tests:** `scripts/test-waf-body-inspection.py` passes 28/28. With the template reverted, 2 tests fail. - **Lab:** verified on haproxy 3.0.11 with QUIC. 18 KB, 265 KB and 417 KB bodies are stamped `partial` on H1, H2 and H3; bodies of 14 KB or less are stamped `complete`. **Deploy:** rebuild the image, then recreate haproxy-manager per host, .148 first. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jknapp added 2 commits 2026-10-19 15:59:52 +00:00
req.body_size is "buffered + htx->extra". In HAProxy 3.0 only the H1 and
H2 muxes set htx->extra from Content-Length; the H3 mux never does, so
over HTTP/3 req.body_size == req.body_len and any body cut at one buffer
(~15 KB) was stamped `complete`. Coraza then failed to parse the
truncated JSON (200002) and WHP's 991900020 returned a 403.

Measured on whp01, Oct 6-19 2026: 73,795 H3 `complete` stamps and 0
`partial`; 7 legitimate requests 403'd (6 Divi sync-to-server saves of
265-417 KB, 1 Groundhogg preview of 18 KB). Reproduced in a lab on
haproxy 3.0.11 with QUIC. H1/H2/H3 all reject a Content-Length that
doesn't match the DATA received, so the header is a protocol-independent
body length. No change on H1/H2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jknapp merged commit b6cc3adfaa into main 2026-10-19 21:22:14 +00:00
jknapp deleted branch fix/waf-h3-body-stamp 2026-10-19 21:22:17 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: cloud-hosting-platform/haproxy-manager-base#12