Commit Graph

2 Commits

Author SHA1 Message Date
c1331a592a waf-block page: escape literal % as %% (HAProxy lf-file expansion)
All checks were successful
HAProxy Manager Build and Push / Build-and-Push (push) Successful in 54s
End-to-end test of the 403 page showed CSS `100%` rendering as `100`
and gradient stops `0%, 100%` rendering as `0, 100` — HAProxy's
`lf-file` directive runs log-format expansion over the file content,
and `%` is the format-escape character. Single `%` is consumed by
the expander.

Doubled every literal CSS percentage (`100%%`, `0%%`, etc.) so HAProxy
emits a single `%` in the rendered body. Format expressions like
`%[unique-id]` and `%[req.hdr(host)]` stay single-`%` — those are the
substitutions we want.

Added a comment block at the top of the file documenting the gotcha for
future editors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 05:48:14 -07:00
d931ab0dbc waf-block: render a real HTML page on Coraza-denied requests
All checks were successful
HAProxy Manager Build and Push / Build-and-Push (push) Successful in 1m55s
Previously a Coraza block returned an empty 403 with only the
`waf-block: request` header — a legitimate site owner caught in a
false-positive had no idea what happened or how to get help.

Now:
- hap_header.tpl: every request gets a unique-id (uuid()) and that ID
  is injected back into the request as X-Request-Reference for the
  backend, so upstream Apache/PHP logs can correlate too.
- hap_listener.tpl: on a request-phase Coraza deny we use
  `http-request return` with `lf-file` instead of `http-request deny`,
  so HAProxy renders the new errors/403-waf.html page with the
  request reference substituted in. The page tells the visitor a
  request was blocked, displays the reference, and points site owners
  to https://secure.anhonesthost.com/submitticket.php to open a ticket
  rather than exposing a public email address (avoids giving
  attackers a flood target).
- The waf-block header and x-request-reference header are still set
  on the response so curl / monitoring clients can pick them up
  without rendering HTML.
- Response-phase deny stays as the bare 403 — outbound blocks are
  rare in our config and an HTML body could land mid-stream.

Errorfile lives at /haproxy/errors/403-waf.html (NOT under
/etc/haproxy/, because that path is a named volume in deployed
containers and would shadow baked-in files on existing deployments).

Support workflow: visitor quotes the reference → support greps
/var/log/haproxy.log for the uuid → gets timestamp + client IP +
Host + URI → greps /var/log/coraza/audit.log for the matching
transaction → reads the rule_id that fired.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 05:41:16 -07:00