fix(haproxy): silence the ACL pattern warning on wp_admin_asset

HAProxy warns on any pattern whose first character is "(", because it
cannot distinguish an intended regex from a fetch-argument list with a
stray space:

  parsing acl 'wp_admin_asset' : matching 'path_reg' for pattern
  '(^|/)wp-admin/...' is likely a mistake and probably not what you want.

"--" is HAProxy's documented end-of-flags marker and is the remedy the
warning itself names. Cosmetic to matching, but not to operations: left
unsilenced it fires on every config load and every reload on every host,
which trains operators to skim past warnings and gives a real one
somewhere to hide.

Matching semantics are unchanged, verified rather than assumed. Both
forms were run side by side as two frontends under real HAProxy
3.0.11-1+deb13u3 and gave identical verdicts on all 8 vectors:

  /wp-admin/css/login.min.css        MATCH   / MATCH
  /wp-admin/js/user-profile.min.js   MATCH   / MATCH
  /wp-admin/images/x.png             MATCH   / MATCH
  /wp-admin/css/sub/deep.css         MATCH   / MATCH
  /blog/wp-admin/css/a.css           MATCH   / MATCH
  /wp-admin/css/x.php                NOMATCH / NOMATCH
  /wp-admin/plugins.php              NOMATCH / NOMATCH
  /some--path/file.css               NOMATCH / NOMATCH

The last vector is the one that matters: it proves HAProxy consumed "--"
as end-of-flags rather than adopting it as the pattern. Had it done the
latter, the ACL would have matched paths containing "--" and stopped
matching css/js -- gating every login page's own stylesheets while the
page itself still returned 200.

wp_admin_path needs no "--" only because its "-i" flag already occupies
the flag slot; it is not otherwise special.

Adds a regression test asserting both the "--" and the pattern it
guards, so this cannot pass by the pattern having been changed. Verified
to fail when the "--" is removed.
This commit is contained in:
2026-08-17 12:54:22 -07:00
parent b931baa9a7
commit 465253c640
2 changed files with 35 additions and 1 deletions
+19
View File
@@ -491,6 +491,25 @@ class UriNormalisation(unittest.TestCase):
self.assertIn('.php', acl_line,
'wp_admin_asset must exclude .php explicitly')
def test_wp_admin_asset_pattern_is_end_of_flags_guarded(self):
"""A pattern starting with "(" makes HAProxy warn on EVERY load/reload:
parsing acl 'wp_admin_asset' : matching 'path_reg' for pattern
'(^|/)wp-admin/...' is likely a mistake ... Maybe you need to
remove the extraneous space before '('.
"--" is the end-of-flags marker HAProxy itself names as the fix. It is
cosmetic to matching but not to operations: an unsilenced warning on
every reload on every host trains people to skim past warnings, which
is how a real one gets missed. Assert the pattern is still the one we
think it is, so this can never pass by the pattern having been changed.
"""
acl_line = require_rule(self.cfg, 'acl wp_admin_asset', 'wp_admin_asset ACL')
self.assertRegex(
acl_line, r'path_reg\s+--\s+\(',
'wp_admin_asset pattern begins with "(" and MUST be preceded by "--"')
self.assertIn('(^|/)wp-admin/(css|js|images)/', acl_line)
def test_case_insensitive_acl_and_regsub_are_kept_in_sync(self):
"""A case-insensitive wp_admin_path with a case-sensitive regsub is an
INFINITE REDIRECT LOOP: regsub finds no "/wp-admin/" in
+16 -1
View File
@@ -473,7 +473,22 @@ frontend web
# substring, so it cannot cause the silent "login page renders unstyled"
# regression that an extension allowlist would risk. Requires PCRE2, which
# both the Debian (deployed) and Alpine haproxy builds have (+PCRE2).
acl wp_admin_asset path_reg (^|/)wp-admin/(css|js|images)/(?!.*\.php).*$
#
# THE "--" IS LOAD-BEARING, DO NOT DELETE IT. HAProxy warns on any pattern
# whose first character is "(", because it cannot tell an intended regex
# from a fetch-argument list someone typo'd a space into:
# parsing acl 'wp_admin_asset' : matching 'path_reg' for pattern
# '(^|/)wp-admin/...' is likely a mistake and probably not what you want.
# Maybe you need to remove the extraneous space before '('.
# "--" is HAProxy's documented end-of-flags marker and is the fix it names
# itself ("please insert '--' between the match and the pattern"). It changes
# no matching semantics -- verified live on whp02: /wp-admin/css/login.min.css
# still passes and /wp-admin/css/x.php is still gated, before and after.
# Left unsilenced this fires on EVERY config load and reload on every host,
# where it trains operators to skim past warnings and can bury a real one.
# wp_admin_path escapes the warning only because its "-i" flag happens to
# consume the flag slot first; it is not otherwise special.
acl wp_admin_asset path_reg -- (^|/)wp-admin/(css|js|images)/(?!.*\.php).*$
acl wp_gate_exempt hdr(host),lower -f /etc/haproxy/wpadmin_gate_exempt.list
# ENCODED SEPARATOR. percent-decode-unreserved deliberately does NOT decode
# %2F -- "/" is a reserved character, and decoding it in the normalizer