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>
This commit is contained in:
2026-05-15 05:48:14 -07:00
parent d931ab0dbc
commit c1331a592a

View File

@@ -1,4 +1,12 @@
<!DOCTYPE html> <!DOCTYPE html>
<!--
Served by HAProxy via `lf-file` on Coraza WAF deny.
IMPORTANT: HAProxy's lf-file expansion treats `%` as the start of a
log-format expression. Literal percent signs (CSS 100%, gradient stops,
url-encoded data, etc.) MUST be doubled as `%%` or HAProxy will silently
swallow them. Expressions like `%[unique-id]` / `%[req.hdr(host)]` stay
single-`%` — those are the substitutions we want.
-->
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
@@ -7,11 +15,11 @@
<title>Request blocked &middot; %[req.hdr(host)]</title> <title>Request blocked &middot; %[req.hdr(host)]</title>
<style> <style>
*, *::before, *::after { box-sizing: border-box; } *, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; } html, body { margin: 0; padding: 0; height: 100%%; }
body { body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
color: #1f2937; color: #1f2937;
background: linear-gradient(135deg, #f9fafb 0%, #eef2f7 100%); background: linear-gradient(135deg, #f9fafb 0%%, #eef2f7 100%%);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -23,7 +31,7 @@
border-radius: 12px; border-radius: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.05), 0 12px 32px rgba(31,41,55,0.08); box-shadow: 0 1px 3px rgba(0,0,0,0.05), 0 12px 32px rgba(31,41,55,0.08);
max-width: 560px; max-width: 560px;
width: 100%; width: 100%%;
padding: 36px 40px; padding: 36px 40px;
} }
.badge { .badge {