fix(edge): coraza-caddy 2.6.1 so streaming responses survive the WAF - #17
Merged
Merged
Conversation
coraza-caddy 2.5.0 propagates Flush() through a bare http.Flusher type
assertion. Caddy wraps the response writer in a responseRecorder whenever
access logging is on - which the panel enables on every node - and that
recorder only exposes flushing via Unwrap(), so every flush was dropped.
Any streaming response behind a WAF-enabled host (SSE, long-poll, chunked
progress) stalled until the buffer filled or the handler returned; the
browser never even got the headers. Blocking vs detection-only made no
difference, only disabling the WAF removed the wrapper.
Upstream fixed it in 2.6.1 ("flush and hijack through the response writer
Unwrap chain", corazawaf/coraza-caddy#344). coraza v3.7.0 and CRS 4.25.0
are unchanged, so the audit log the node-agent parses is unaffected.
2.6.1 needs Go 1.26; caddy:2.11.4-builder ships golang:1.26-alpine.
Verified with caddy 2.11.4 + the real generated route + srv0 access logs:
2.5.0 -> curl gets 0 SSE events, headless Chrome shows /api/realtime
pending forever on a Beszel hub; 2.6.1 -> events flow, dashboard
subscribes (204). Blocking mode still hits CRS 942100 on "systems/*",
which #15 covers.
Refs #14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #14 - second, separate bug behind the report. #15 fixed the blocking-mode false positive; this one is why the reporter's workaround and detection-only mode still did not help.
Root cause
coraza-caddy 2.5.0 (edge image up to v1.4.8) propagates
Flush()with a barehttp.Flushertype assertion. Caddy wraps the response writer in aresponseRecorderwhenever access logging is on, and the panel enables access logs on every node'ssrv0. That recorder exposes flushing only throughUnwrap(), so every flush was silently dropped. Any streaming response behind a WAF-enabled host stalled until the server buffer filled or the handler returned: an EventSource never received its headers. Blocking vs detection-only made no difference; only disabling the WAF removed the wrapper.My earlier repro used
BuildRouteoutput inside a bare server config withoutlogs, which is why it did not show up. Lesson recorded: reproduce node issues with the full generated server config.Fix
Bump the edge image pin to coraza-caddy 2.6.1 - upstream fix corazawaf/coraza-caddy#344 "flush and hijack through the response writer Unwrap chain". coraza v3.7.0 and CRS 4.25.0 are unchanged, so the audit log the node-agent parses is unaffected. 2.6.1 needs Go 1.26;
caddy:2.11.4-buildershipsgolang:1.26-alpine.Verification (caddy 2.11.4, real generated route,
srv0.logson)GET /api/realtimepending foreverGET200, subscriptionsPOST204POST403 (CRS 942100, covered by #15)SecRuleRemoveById 942100POST204One quirk of 2.6.1, documented in
docs/WAF.md: the first flush is forwarded only after the handler wrote body bytes, so an endpoint that sends headers and then stays silent delivers them with its first event. Mainstream SSE servers send an opening event immediately.Note: PR CI builds only the app Dockerfile; the edge image is built by the release workflow at tag time. The xcaddy build with 2.6.1 was done locally with Go 1.26.3 and is what the numbers above ran on. Users need the v1.4.9 edge image on every node - no config change.