Skip to main content

Command Palette

Search for a command to run...

Inside a WordPress Bot Detection Engine

Additive threat scoring, MITRE path matching, and HMAC-signed SHA-256 proof of work.

Updated
11 min readView as Markdown
Inside a WordPress Bot Detection Engine

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