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 Accept-Language is a strong signal. Every real browser sends it. Most HTTP libraries don't unless explicitly configured.
MITRE ATT&CK path matching
This is one of the more distinctive pieces. Rather than maintaining an arbitrary blocklist of "bad" URLs, detection is organized by attacker tactic:
Credential Access (TA0006):
.env, wp-config.php, .git/, *.sql → +30 points
Collection (TA0009):
Backup files, database dumps → +25 points
Reconnaissance (TA0043):
Admin probes, user enumeration → +20 points
Discovery (TA0007):
Debug endpoints, phpinfo, server-status → +20 points
When an IP requests /wp-config.php.bak, then /.env, then /.git/config, each request scores individually while the rate limiter tracks velocity. The combined effect is rapid escalation to the blocking threshold.
The mapping isn't only for scoring. It surfaces in the detections table, so you can see a blocked IP was performing credential access reconnaissance rather than just "requesting bad URLs." The categorization tells you what attackers are actually looking for.