mcp: session-notes — Contributor wp-admin lockout debug — Cloudflare cache bypass cookie fix
This commit is contained in:
parent
7923d07c20
commit
2c0d0d54eb
@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
created: '2026-06-29'
|
||||||
|
path: Sources/Dev
|
||||||
|
project: cloudflare-cache-strategy
|
||||||
|
tags:
|
||||||
|
- pbs
|
||||||
|
- cloudflare
|
||||||
|
- caching
|
||||||
|
- wordpress
|
||||||
|
- wordfence
|
||||||
|
- ultimate-member
|
||||||
|
- redis
|
||||||
|
- auth
|
||||||
|
type: session-notes
|
||||||
|
---
|
||||||
|
|
||||||
|
## Outcome
|
||||||
|
|
||||||
|
A content contributor (Italy-based, username `sheridanbf731`) was repeatedly locked out of `/wp-admin/` on production. What presented as an "IP block / login" problem turned out to be **three stacked, unrelated issues** that had to be peeled apart in sequence. All three were resolved; contributor confirmed able to log in, move between front-end and admin, and log out without being blocked.
|
||||||
|
|
||||||
|
Root causes, in the order they were found and fixed:
|
||||||
|
|
||||||
|
1. **Editor role bounced from wp-admin** — Ultimate Member's `um_block_wpadmin_by_user_role` (hooked to `init` @ priority 99) was enforcing a stale "Can access wp-admin? No" value. The per-role setting had been flipped to **Yes** and the DB + UM debug panel both showed Yes, but the function was reading a **stale Redis-cached role object**. `wp cache flush` cleared it and the bounce stopped. (The role setting was correct three steps before the fix actually took — the cache was the blocker.)
|
||||||
|
|
||||||
|
2. **`reauth=1` session-invalidation loop + Wordfence lockout cascade** — the real recurring problem. A Cloudflare cache rule ("Bypass cache for admin") only bypassed cache for `/wp-admin` and `/wp-login.php` **paths**. Logged-in users browsing **front-end** pages (homepage, recipes — where UM/role redirects send them) were served **cached, logged-out** pages. WordPress saw the cookie/cache mismatch, threw `reauth=1`, and background `admin-ajax.php` requests fired with the confused session state. Wordfence logged those AJAX requests as **failed logins** → two of them tripped the brute-force lockout. Fixed by adding `http.cookie contains "wordpress_logged_in_"` to the cache-bypass rule, so Cloudflare never serves cache to any logged-in user on any path.
|
||||||
|
|
||||||
|
3. **Two broken conditions in the same Cloudflare rule** — the n8n and gitea bypass conditions used `URI Path contains "https://n8n..."`, which can never match (a URI path never contains a hostname). Corrected syntax is `http.host eq "n8n.plantbasedsoutherner.com"`. Travis chose to leave the originals in place for now rather than edit them tonight; the Host-based correction is captured here as a follow-on.
|
||||||
|
|
||||||
|
## Topics Covered
|
||||||
|
|
||||||
|
- Wordfence block-page diagnosis vs. manual block list (the IP was never manually blocked; it was a failed-login lockout)
|
||||||
|
- **Cloudflare IPs in logs**: all visitor traffic appears to Wordfence as Cloudflare edge IPs (`172.69.x.x`, `172.70.x.x`), rotating and geolocated to wherever the edge node is (Milan, Tallinn). Wordfence counts failed logins **per IP**, so a shared Cloudflare IP pools everyone's failures onto one counter, making lockouts fire "faster than the threshold."
|
||||||
|
- Ultimate Member two-layer access control: per-role "Can access wp-admin?" toggle vs. role priority (UM uses the highest-priority role's settings when a user has multiple). Contributor had a single role, so priority wasn't the issue here.
|
||||||
|
- Redirect-source diagnosis: `x-redirect-by: WordPress` response header indicates a core `wp_redirect()` call (theme/snippet/mu-plugin) rather than a self-labeling plugin.
|
||||||
|
- `wp eval` hook-dump technique to list every callback on `init` / `admin_init` / `template_redirect` with file+line for closures — this is what surfaced `um_block_wpadmin_by_user_role` by name.
|
||||||
|
- Confirmed clean: all WPCode snippets, stock Astra theme + functions.php, mu-plugins (only Elementor Safe Mode), and `grep -rni "setcookie"` across themes/mu-plugins (empty — confirms cookie-busting code was replaced by the n8n publish-webhook approach).
|
||||||
|
- The Cloudflare "Verify you are human" challenge on `/wp-login.php` (Turnstile/managed challenge, `__cf_chl_f_tk` token) is **normal, benign** bot protection — risk-scored per session, so trusted browsers skip it and fresh/private sessions get it. Not part of the bug.
|
||||||
|
|
||||||
|
## Key Learnings
|
||||||
|
|
||||||
|
- **When a role/permission change "doesn't take" but the setting clearly reads correct, flush the object cache (Redis) before assuming the setting is wrong.** Object cache + role meta is a classic stale-read trap. This alone would have saved many steps.
|
||||||
|
- **Cache-bypass for logged-in WordPress users must be keyed on the `wordpress_logged_in_` cookie, not on URL path.** Path-only bypass leaves front-end pages cacheable, which poisons logged-in sessions and produces `reauth=1` loops. This is the single most important fix from the session.
|
||||||
|
- **Wordfence behind Cloudflare must be told how to read real client IPs** (Settings → "How does Wordfence get IPs?" → CF-Connecting-IP / X-Forwarded-For). Until set, all per-IP logic (lockouts, blocks, allowlists) operates on shared Cloudflare edge IPs and behaves erratically. **Do not allowlist or block `172.69.x.x` / `172.70.x.x` ranges — those are Cloudflare, not visitors.** This remained un-done at session end and is the top follow-on.
|
||||||
|
- Cloudflare rule field discipline: **URI Path** is the path only; matching a subdomain requires the **Host** field (`http.host eq ...`).
|
||||||
|
- Stacked symptoms can share one root: the "rotating IPs," "faster than 3 attempts," and "blocked on logout" complaints were all downstream of the same Cloudflare-cache + shared-IP situation.
|
||||||
|
|
||||||
|
## Follow-ons
|
||||||
|
|
||||||
|
- [ ] **Set Wordfence "How does Wordfence get IPs?"** to the CF-Connecting-IP (or X-Forwarded-For) option so Wordfence sees real client IPs. Highest-value remaining cleanup; safe to do any day. Until then, per-IP blocks/allowlists are unreliable.
|
||||||
|
- [ ] **Fix the n8n/gitea cache-bypass conditions** in the "Bypass cache for admin" rule — switch from the non-functional `URI Path contains "https://..."` to `http.host eq "n8n.plantbasedsoutherner.com"` / `http.host eq "gitea.plantbasedsoutherner.com"`. Test on staging first; verify n8n + gitea still load and that cache still bypasses correctly for them.
|
||||||
|
- [ ] **Rotate the hardcoded n8n webhook secret** found in the `publish_post` WPCode snippet (HMAC secret was exposed in chat) and move it to `wp-config.php` or an env var instead of inline in the snippet.
|
||||||
|
- [ ] Consider replicating the corrected Cloudflare cache rule on **staging** and validating the full logged-in cycle there before treating the production fix as final.
|
||||||
|
- [ ] Optional: if the `/wp-login.php` human-check becomes friction for the three known contributors, tune via a Cloudflare WAF skip rule for known users — low priority, currently working as intended.
|
||||||
Loading…
Reference in New Issue
Block a user