Skip to content

Commit 75bbc03

Browse files
authored
feat: reserved WebDecoy-Test User-Agent always records a test detection (#80)
curl -A "WebDecoy-Test/1.0" https://your-site.example/ now always produces a detection (WebDecoy/app#677): logged locally on every install, submitted to the cloud on connected ones, and answered with a 403 JSON receipt so the curl output itself shows the plugin acted. Checked before the allowlist (a developer testing from an allowlisted IP still gets their receipt), before the block check, and before rules: a test must always fire and must never trip enforcement or the critical-moment alert. The cloud labels these rows as tests and keeps them out of stats and billing.
1 parent 26085c3 commit 75bbc03

1 file changed

Lines changed: 71 additions & 0 deletions

File tree

webdecoy.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,6 +1114,18 @@ public function early_check(): void
11141114
$blocker = new WebDecoy_Blocker();
11151115
$ip = $this->get_client_ip();
11161116

1117+
// Reserved test trigger (WebDecoy/app#677):
1118+
// curl -A "WebDecoy-Test/1.0" https://your-site.example/
1119+
// always records a detection, so a fresh install can prove itself end
1120+
// to end. Deliberately checked before the allowlist (a developer
1121+
// testing from an allowlisted IP still gets their receipt), before the
1122+
// block check, and before rules (a test must never trip enforcement).
1123+
// The cloud labels these rows as tests and keeps them out of stats.
1124+
if ($this->is_test_trigger_request()) {
1125+
$this->handle_test_trigger($ip);
1126+
return;
1127+
}
1128+
11171129
// Allowlisted IPs bypass all detection entirely.
11181130
if ($blocker->is_allowlisted($ip)) {
11191131
return;
@@ -1911,6 +1923,65 @@ private function submit_detection(\WebDecoy\DetectionResult $result, string $ip)
19111923
$client->submitDetection($detection);
19121924
}
19131925

1926+
/**
1927+
* Whether this request carries the reserved test User-Agent
1928+
* (prefix "WebDecoy-Test/", case-insensitive). See WebDecoy/app#677.
1929+
*/
1930+
private function is_test_trigger_request(): bool
1931+
{
1932+
if (!isset($_SERVER['HTTP_USER_AGENT'])) {
1933+
return false;
1934+
}
1935+
$ua = ltrim(sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])));
1936+
return stripos($ua, 'WebDecoy-Test/') === 0;
1937+
}
1938+
1939+
/**
1940+
* Record the reserved test detection and answer with an unambiguous
1941+
* receipt (WebDecoy/app#677).
1942+
*
1943+
* Logs locally always, submits to the cloud when connected — the same two
1944+
* paths a real detection takes — then responds 403 so the curl output
1945+
* itself shows the plugin acted. Never blocks the IP, never trips rules:
1946+
* a test must have no consequences beyond its own row.
1947+
*
1948+
* @param string $ip
1949+
*/
1950+
private function handle_test_trigger(string $ip): void
1951+
{
1952+
global $wpdb;
1953+
1954+
$result = new \WebDecoy\DetectionResult(100, ['test_trigger']);
1955+
1956+
// Local log, inlined rather than via log_detection(): that path also
1957+
// queues the critical-moment alert for CRITICAL rows, and a test must
1958+
// not page anyone.
1959+
$wpdb->insert($wpdb->prefix . 'webdecoy_detections', [
1960+
'ip_address' => $ip,
1961+
'user_agent' => isset($_SERVER['HTTP_USER_AGENT']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_USER_AGENT'])) : '',
1962+
'score' => $result->getScore(),
1963+
'threat_level' => $result->getThreatLevel(),
1964+
'source' => 'wordpress_plugin',
1965+
'flags' => json_encode(['flags' => $result->getFlags(), 'metadata' => ['test' => true]]),
1966+
'created_at' => gmdate('Y-m-d H:i:s'),
1967+
]);
1968+
1969+
try {
1970+
$this->submit_detection($result, $ip);
1971+
} catch (\Exception $e) {
1972+
error_log('WebDecoy API error: ' . $e->getMessage());
1973+
}
1974+
1975+
nocache_headers();
1976+
status_header(403);
1977+
header('Content-Type: application/json; charset=utf-8');
1978+
echo wp_json_encode([
1979+
'webdecoy_test' => true,
1980+
'message' => 'Test detection recorded. Check your WebDecoy dashboard.',
1981+
]);
1982+
exit;
1983+
}
1984+
19141985
/**
19151986
* Check comment submission
19161987
*

0 commit comments

Comments
 (0)