Inside a WordPress Bot Detection Engine
Additive threat scoring, MITRE path matching, and HMAC-signed SHA-256 proof of work.

The WebDecoy WordPress plugin ships with zero configuration required. But underneath the "install, activate, done" experience is a multi-layer detection engine that scores every request across server-side signals, client-side fingerprints, behavioral analysis, and proof-of-work verification.
This post walks through how each layer works, how they combine into a single threat score, and why this architecture catches bots that simpler approaches miss. It's a WordPress plugin, but the scoring design applies to any request pipeline.
The Detection Pipeline
Every request flows through a pipeline that evaluates it before WordPress processes it:
Incoming Request
│
├─ Is this IP blocked? → Yes → Block page
│
├─ Is this a known good bot? → Verify via reverse DNS → Allow
│
├─ Server-side analysis
│ ├─ User-Agent patterns
│ ├─ HTTP header consistency
│ ├─ MITRE ATT&CK path matching
│ └─ Rate limit check
│
├─ Client-side signals (on form submission)
│ ├─ WebDriver / headless detection
│ ├─ Automation framework markers
│ ├─ Canvas / WebGL fingerprint
│ └─ Behavioral scoring
│
├─ Proof-of-Work verification (on form submission)
│ └─ SHA-256 challenge validation
│
└─ Score aggregation → Allow / Challenge / Block
The first two checks are fast exits. Blocked IPs get rejected immediately. Verified good bots skip detection entirely. Everything else gets scored.
Threat Scoring: 0 to 100
Every detection signal adds points to a threat score. The score determines what happens to the request:
| Score Range | Severity | Action |
|---|---|---|
| 0–19 | Minimal | Allow (likely human) |
| 20–39 | Low | Log only |
| 40–59 | Medium | Optional challenge |
| 60–74 | High | Challenge or block |
| 75–100 | Critical | Automatic block |
The default blocking threshold is 75, configurable in settings. Scores at 40 and above are logged for review.
The scoring is additive. A request doesn't need to fail one dramatic test — it accumulates evidence across multiple signals. A slightly suspicious user agent (+25) combined with missing cookies (+15) and an unusual request path (+20) adds up to 60, enough to trigger a challenge. No single signal is conclusive, but the combination tells a clear story.
Base scores for common signals:
Missing standard headers: 10-30
No cookies on non-first visit: 15
Suspicious user agent: 25
Known bot user agent: 50
curl / wget / python-requests: 35
Automation tool detected: 40
Headless browser markers: 25
Rate limit exceeded: 25
Honeypot field triggered: 60
Fake bot (failed DNS verify): 80
A real Chrome browser hitting a normal page scores near zero. A Python script with a spoofed user agent, no cookies, and missing standard headers quickly crosses the blocking threshold.
Server-Side Analysis
User-Agent and header consistency
The plugin checks the User-Agent against known bot patterns (curl, wget, python-requests, Go-http-client, scrapy, and dozens more) and evaluates header consistency. Real browsers send a predictable set of headers — Accept, Accept-Language, Accept-Encoding, Connection — in a consistent order. Automated tools frequently omit headers or send them in unusual combinations.
Missing
