Skip to content

fix: BotDetector no longer trusts forwarded headers it was never told to trust - #111

Merged
cport1 merged 1 commit into
mainfrom
fix/botdetector-trusted-ip-85
Oct 4, 2026
Merged

cport1 merged 1 commit into
mainfrom
fix/botdetector-trusted-ip-85

Conversation

@cport1

@cport1 cport1 commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Closes #85. Refs WebDecoy/app#875.

What was wrong

BotDetector::analyze() falls back to its own resolver when the signals it gets have no ip_address. That private getClientIP() took CF-Connecting-IP, then the leftmost X-Forwarded-For, then X-Real-IP, from any client, with no trusted-proxy check. The resolved address is the one a claimed good bot is reverse-DNS verified against.

While checking that both entry points resolve the same request identically, I found a second gap. WebDecoy_Detector (used by WooCommerce checkout and webdecoy.php:1207; the plugin's own get_detector() already passed them) built its BotDetector without trusted_proxies. On a site behind Cloudflare it collected the edge address as ip_address, so a real Googlebot was checked against a Cloudflare IP.

Change

  • analyze() keeps a supplied ip_address only when it is a valid IP. Otherwise it calls $this->signalCollector->getIP(). The second resolver is deleted. There is no cURL and no new dependency.
  • WebDecoy_Detector passes webdecoy_plugin_trusted_proxies(), the same list get_client_ip() and the blocker use.

Compatibility: a site with no trusted proxies configured now verifies bots against REMOTE_ADDR instead of a client-supplied header. That is the intended secure default and matches SignalCollector. A site behind an unconfigured proxy already gets the existing misconfiguration notice.

Tests

tests/BotDetectorIpTest.php observes the IP that analyze() verifies against, through a GoodBotList spy, so no DNS lookups run. It covers:

  • no proxy trust, with spoofed CF / XFF / X-Real-IP
  • a direct client spoofing past a proxy that is configured elsewhere
  • a trusted IPv4 proxy, where the XFF chain is walked right to left
  • trusted and untrusted IPv6 chains
  • a valid supplied ip_address being kept, and an invalid one being resolved
  • BotDetector and SignalCollector giving the same answer for the same request
  • the WordPress wrapper picking up the configured proxies

All 7 of the new tests fail on main. With this change the full suite passes, 203 of 203. PHPStan reports no errors, and phpcs shows no new warnings on the touched files.

… to trust

When signals arrived without ip_address, BotDetector::analyze() fell back to a
private resolver that took CF-Connecting-IP or the leftmost X-Forwarded-For
from any client. That address is the one a claimed good bot is verified
against. It now uses SignalCollector::getIP(), the trusted-proxy-aware resolver
the rest of the plugin uses. A valid supplied ip_address is still kept.

WebDecoy_Detector also built its BotDetector without the trusted proxies, so
on a site behind Cloudflare it verified good bots against the edge address.
It now passes the configured proxies.

Closes #85. Refs WebDecoy/app#875.
@cport1
cport1 merged commit faf0568 into main Oct 4, 2026
4 checks passed
@cport1 cport1 mentioned this pull request Oct 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remove BotDetector’s untrusted forwarded-IP fallback

1 participant