prefix . 'honeypot_log'; } public static function install() { global $wpdb; $table = self::table(); $charset_collate = $wpdb->get_charset_collate(); $sql = "CREATE TABLE {$table} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, blocked_at DATETIME NOT NULL, ip_address VARCHAR(45) NOT NULL DEFAULT '', form_type VARCHAR(100) NOT NULL DEFAULT '', reason VARCHAR(255) NOT NULL DEFAULT '', request_uri VARCHAR(1000) NOT NULL DEFAULT '', user_agent TEXT NOT NULL, PRIMARY KEY (id), KEY ip_address (ip_address), KEY blocked_at (blocked_at), KEY form_type (form_type) ) {$charset_collate};"; require_once ABSPATH . 'wp-admin/includes/upgrade.php'; dbDelta($sql); update_option(self::TABLE_VERSION_OPTION, self::TABLE_VERSION); } public static function insert(array $data) { global $wpdb; $wpdb->insert( self::table(), [ 'blocked_at' => current_time('mysql'), 'ip_address' => sanitize_text_field($data['ip'] ?? ''), 'form_type' => sanitize_text_field($data['form'] ?? 'Unknown'), 'reason' => sanitize_text_field($data['reason'] ?? ''), 'request_uri' => esc_url_raw(substr($data['uri'] ?? '', 0, 1000)), 'user_agent' => sanitize_textarea_field($data['ua'] ?? ''), ], ['%s', '%s', '%s', '%s', '%s', '%s'] ); } public static function get_rows(array $args = []): array { global $wpdb; $table = self::table(); $limit = max(1, intval($args['per_page'] ?? 25)); $offset = max(0, intval($args['offset'] ?? 0)); $where = '1=1'; $params = []; if (!empty($args['ip'])) { $where .= ' AND ip_address = %s'; $params[] = sanitize_text_field($args['ip']); } if (!empty($args['form'])) { $where .= ' AND form_type = %s'; $params[] = sanitize_text_field($args['form']); } if (!empty($args['search'])) { $like = '%' . $wpdb->esc_like($args['search']) . '%'; $where .= ' AND (ip_address LIKE %s OR user_agent LIKE %s OR reason LIKE %s)'; $params[] = $like; $params[] = $like; $params[] = $like; } $params[] = $limit; $params[] = $offset; $sql = "SELECT * FROM {$table} WHERE {$where} ORDER BY blocked_at DESC LIMIT %d OFFSET %d"; return $wpdb->get_results($wpdb->prepare($sql, $params)) ?: []; } public static function count(array $args = []): int { global $wpdb; $table = self::table(); $where = '1=1'; $params = []; if (!empty($args['ip'])) { $where .= ' AND ip_address = %s'; $params[] = sanitize_text_field($args['ip']); } if (!empty($args['form'])) { $where .= ' AND form_type = %s'; $params[] = sanitize_text_field($args['form']); } if (!empty($args['search'])) { $like = '%' . $wpdb->esc_like($args['search']) . '%'; $where .= ' AND (ip_address LIKE %s OR user_agent LIKE %s OR reason LIKE %s)'; $params[] = $like; $params[] = $like; $params[] = $like; } $sql = "SELECT COUNT(*) FROM {$table} WHERE {$where}"; return (int) $wpdb->get_var($params ? $wpdb->prepare($sql, $params) : $sql); } public static function get_form_types(): array { global $wpdb; return $wpdb->get_col("SELECT DISTINCT form_type FROM " . self::table() . " ORDER BY form_type ASC") ?: []; } public static function clear(): void { global $wpdb; $wpdb->query("TRUNCATE TABLE " . self::table()); } public static function delete_older_than_days(int $days): void { global $wpdb; $wpdb->query( $wpdb->prepare( "DELETE FROM " . self::table() . " WHERE blocked_at < DATE_SUB(NOW(), INTERVAL %d DAY)", $days ) ); } } /* ====================================================================== * CENTRAL API CLIENT * Queues blocked submissions and batch-sends to a central dashboard. * ====================================================================*/ class SmartHoneypotAPIClient { const OPT_SETTINGS = 'hp_api_settings'; const OPT_QUEUE = 'hp_api_queue'; const QUEUE_MAX = 500; const BATCH_SIZE = 50; public static function defaults(): array { return [ 'enabled' => false, 'api_url' => '', 'last_sync' => 0, 'sent_total' => 0, ]; } public static function settings(): array { return wp_parse_args(get_option(self::OPT_SETTINGS, []), self::defaults()); } /** Called from log_spam() — very fast, just appends to option. */ public static function enqueue(array $data): void { $s = self::settings(); if (!$s['enabled'] || empty($s['api_url'])) { return; } $queue = (array) get_option(self::OPT_QUEUE, []); if (count($queue) >= self::QUEUE_MAX) { array_shift($queue); // drop oldest when full } $queue[] = $data; update_option(self::OPT_QUEUE, $queue, false); // no autoload } /** Called by WP-cron every 5 minutes. Sends pending batch to the API. */ public static function flush(): void { $s = self::settings(); if (!$s['enabled'] || empty($s['api_url'])) { return; } $queue = (array) get_option(self::OPT_QUEUE, []); if (empty($queue)) { return; } $batch = array_splice($queue, 0, self::BATCH_SIZE); $site_hash = hash('sha256', home_url()); $response = wp_remote_post( trailingslashit(esc_url_raw($s['api_url'])) . 'api/v1/submit', [ 'timeout' => 15, 'blocking' => true, 'headers' => ['Content-Type' => 'application/json'], 'body' => wp_json_encode([ 'site_hash' => $site_hash, 'blocks' => $batch, ]), ] ); if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) === 200) { update_option(self::OPT_QUEUE, $queue, false); $s['last_sync'] = time(); $s['sent_total'] = ($s['sent_total'] ?? 0) + count($batch); update_option(self::OPT_SETTINGS, $s); } } /** Number of items currently waiting to be sent. */ public static function queue_size(): int { return count((array) get_option(self::OPT_QUEUE, [])); } } /* ====================================================================== * ADMIN PAGE * ====================================================================*/ class SmartHoneypotAdmin { const MENU_SLUG = 'honeypot-logs'; const NONCE_ACTION = 'hp_admin_action'; const PER_PAGE = 25; public static function register() { add_action('admin_menu', [self::class, 'add_menu']); add_action('admin_init', [self::class, 'handle_actions']); add_action('admin_enqueue_scripts', [self::class, 'enqueue_styles']); add_filter('plugin_action_links_' . plugin_basename(HP_PLUGIN_FILE), [self::class, 'plugin_links']); } public static function plugin_links($links) { array_unshift($links, 'View Logs'); $links[] = 'Documentation'; return $links; } public static function add_menu() { add_menu_page( 'Honeypot Logs', 'Honeypot Logs', 'manage_options', self::MENU_SLUG, [self::class, 'render_page'], 'dashicons-shield-alt', 81 ); } public static function enqueue_styles($hook) { if ($hook !== 'toplevel_page_' . self::MENU_SLUG) { return; } wp_add_inline_style('common', ' #hp-wrap { max-width:1400px; } #hp-wrap .hp-tabs { margin:16px 0 0; } #hp-wrap .hp-stats { display:flex; gap:14px; margin:16px 0; flex-wrap:wrap; } #hp-wrap .hp-stat-card { background:#fff; border:1px solid #c3c4c7; border-radius:4px; padding:14px 22px; min-width:130px; text-align:center; } #hp-wrap .hp-stat-num { font-size:2em; font-weight:700; color:#2271b1; line-height:1.2; } #hp-wrap .hp-stat-lbl { color:#646970; font-size:12px; } #hp-wrap .hp-filters { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:12px; } #hp-wrap .hp-filters input, #hp-wrap .hp-filters select { height:32px; } #hp-wrap table.hp-log { width:100%; border-collapse:collapse; background:#fff; } #hp-wrap table.hp-log th { background:#f0f0f1; padding:8px 12px; text-align:left; border-bottom:2px solid #c3c4c7; white-space:nowrap; } #hp-wrap table.hp-log td { padding:8px 12px; border-bottom:1px solid #f0f0f1; vertical-align:top; } #hp-wrap table.hp-log tr:hover td { background:#f6f7f7; } #hp-wrap .hp-ua { font-size:11px; color:#646970; max-width:300px; word-break:break-all; } #hp-wrap .hp-badge { display:inline-block; padding:2px 8px; border-radius:3px; font-size:11px; font-weight:600; background:#ffecec; color:#b32d2e; border:1px solid #f7c5c5; } #hp-wrap .hp-pager { margin:12px 0; display:flex; align-items:center; gap:8px; } #hp-wrap .hp-pager a, #hp-wrap .hp-pager span { display:inline-block; padding:4px 10px; border:1px solid #c3c4c7; border-radius:3px; background:#fff; text-decoration:none; color:#2271b1; } #hp-wrap .hp-pager span.current { background:#2271b1; color:#fff; border-color:#2271b1; } #hp-wrap .hp-red { color:#b32d2e; } #hp-wrap .hp-api-status { display:inline-flex; align-items:center; gap:6px; font-weight:600; } #hp-wrap .hp-api-status .dot { width:10px; height:10px; border-radius:50%; display:inline-block; } #hp-wrap .dot-on { background:#00a32a; } #hp-wrap .dot-off { background:#646970; } '); } public static function handle_actions() { if (!isset($_POST['hp_action']) || !check_admin_referer(self::NONCE_ACTION)) { return; } if (!current_user_can('manage_options')) { wp_die('Unauthorized'); } if ($_POST['hp_action'] === 'clear_logs') { SmartHoneypotDB::clear(); wp_redirect(add_query_arg(['page' => self::MENU_SLUG, 'tab' => 'logs', 'cleared' => 1], admin_url('admin.php'))); exit; } if ($_POST['hp_action'] === 'save_api_settings') { $current = SmartHoneypotAPIClient::settings(); $new = [ 'enabled' => !empty($_POST['hp_api_enabled']), 'api_url' => esc_url_raw(trim($_POST['hp_api_url'] ?? '')), 'last_sync' => $current['last_sync'], 'sent_total' => $current['sent_total'], ]; update_option(SmartHoneypotAPIClient::OPT_SETTINGS, $new); wp_redirect(add_query_arg(['page' => self::MENU_SLUG, 'tab' => 'settings', 'saved' => 1], admin_url('admin.php'))); exit; } if ($_POST['hp_action'] === 'flush_queue') { SmartHoneypotAPIClient::flush(); wp_redirect(add_query_arg(['page' => self::MENU_SLUG, 'tab' => 'settings', 'flushed' => 1], admin_url('admin.php'))); exit; } } public static function render_page() { if (!current_user_can('manage_options')) { return; } $tab = sanitize_key($_GET['tab'] ?? 'logs'); ?>

Honeypot Fields

Logs cleared.

API settings saved.

Queue flushed to central API.

$filter_ip, 'form' => $filter_form, 'search' => $search, 'per_page' => $per_page, 'offset' => $offset, ]); $rows = SmartHoneypotDB::get_rows($qargs); $total = SmartHoneypotDB::count($qargs); $total_ever = SmartHoneypotDB::count(); $form_types = SmartHoneypotDB::get_form_types(); $total_pages = max(1, ceil($total / $per_page)); global $wpdb; $unique_ips = (int) $wpdb->get_var("SELECT COUNT(DISTINCT ip_address) FROM " . SmartHoneypotDB::table()); $today = (int) $wpdb->get_var("SELECT COUNT(*) FROM " . SmartHoneypotDB::table() . " WHERE blocked_at >= CURDATE()"); $base = admin_url('admin.php?page=' . self::MENU_SLUG . '&tab=logs'); ?>
Total Blocked
Today
Unique IPs
Form Types Hit
Reset

Showing result (page of )

#Date / TimeIP AddressForm TypeReasonURIUser Agent
No blocked attempts recorded yet.
id) ?> blocked_at) ?> ip_address) ?>
filter  lookup ↗
form_type) ?> reason) ?> request_uri) ?> user_agent) ?>
1): ?>
1): ?> ← Prev $p]))); ?> Next →

Central API Settings

Submit blocked attempts anonymously to a central dashboard for aggregate threat intelligence. Only anonymised data is sent: masked IPs (first 2 octets only), form type, block reason, and UA family. No site URL, no full IPs.

Enable Submission
API Endpoint URL

Base URL of your Honeypot API Docker container.


Submission Status

Status
Last Sync
Total Sent blocks
Queue Size pending blocks
Next Auto-Flush
0 && $s['enabled'] && $s['api_url']): ?>
secret = wp_hash('honeypot_plugin_secret_v2'); $this->hp_name = 'website_url_confirm'; $this->token_name = 'form_session_id'; $this->time_name = 'form_render_ts'; add_action('init', [$this, 'init']); } /* ── Init ──────────────────────────────────────────────────────── */ public function init() { if (is_admin()) { add_action('admin_notices', [$this, 'activation_notice']); return; } // Inject honeypot add_filter('the_content', [$this, 'add_to_content_forms'], 99); add_action('comment_form_after_fields', [$this, 'echo_honeypot']); add_action('comment_form_logged_in_after', [$this, 'echo_honeypot']); add_action('woocommerce_register_form', [$this, 'echo_honeypot']); add_action('woocommerce_login_form', [$this, 'echo_honeypot']); add_action('woocommerce_after_order_notes',[$this, 'echo_honeypot']); add_action('register_form', [$this, 'echo_honeypot']); add_action('login_form', [$this, 'echo_honeypot']); add_action('elementor_pro/forms/render_field', [$this, 'add_to_elementor_form'], 10, 2); add_action('elementor/widget/render_content', [$this, 'filter_elementor_widget'], 10, 2); add_filter('gform_form_tag', [$this, 'add_to_gravity_forms'], 10, 2); add_filter('wpcf7_form_elements', [$this, 'add_to_cf7']); add_filter('get_search_form', [$this, 'add_to_search_form'], 99); // Validate add_filter('woocommerce_process_registration_errors', [$this, 'validate_wc_registration'], 10, 4); add_filter('woocommerce_process_login_errors', [$this, 'validate_wc_login'], 10, 3); add_action('woocommerce_after_checkout_validation', [$this, 'validate_wc_checkout'], 10, 2); add_filter('registration_errors', [$this, 'validate_wp_registration'], 10, 3); add_filter('preprocess_comment', [$this, 'validate_comment']); add_action('elementor_pro/forms/validation', [$this, 'validate_elementor_form'], 10, 2); add_action('template_redirect', [$this, 'validate_generic_post']); // CSS & JS add_action('wp_head', [$this, 'print_css']); add_action('wp_footer', [$this, 'print_js'], 99); } /* ── Honeypot HTML ─────────────────────────────────────────────── */ private function get_honeypot_html(): string { return sprintf( '', esc_attr($this->hp_name), esc_attr($this->token_name), esc_attr($this->time_name), esc_attr(time()) ); } public function echo_honeypot() { echo $this->get_honeypot_html(); } /* ── Injection helpers ─────────────────────────────────────────── */ public function add_to_content_forms($content) { if (is_admin() || is_feed()) { return $content; } return preg_replace_callback( '/(]*>)(.*?)(<\/form>)/is', fn($m) => $m[1] . $m[2] . $this->get_honeypot_html() . $m[3], $content ); } public function add_to_search_form($form) { return preg_replace('/(<\/form>)/i', $this->get_honeypot_html() . '$1', $form, 1); } public function add_to_elementor_form($field, $instance) { static $done = false; if (!$done && $field['type'] === 'submit') { $done = true; echo $this->get_honeypot_html(); } } public function filter_elementor_widget($content, $widget) { if ($widget->get_name() === 'form') { $content = preg_replace('/(<\/form>)/i', $this->get_honeypot_html() . '$1', $content, 1); } return $content; } public function add_to_gravity_forms($tag, $form) { return preg_replace('/(
self::MAX_SUBMIT_TIME) { $this->log_spam("Timestamp expired ({$diff}s)"); return false; } } $this->clean_post_data(); return true; } private function validate_js_token(string $token): bool { $parts = explode('|', $token); if (count($parts) !== 2) { return false; } $expected = hash_hmac('sha256', $parts[0] . '|honeypot_js_proof', $this->secret); return hash_equals(substr($expected, 0, 16), $parts[1]); } private function clean_post_data() { foreach ([$this->hp_name, $this->token_name, $this->time_name] as $k) { unset($_POST[$k], $_REQUEST[$k]); } } private function log_spam(string $reason) { $ip = $_SERVER['REMOTE_ADDR'] ?? ''; $uri = $_SERVER['REQUEST_URI'] ?? ''; $ua = $_SERVER['HTTP_USER_AGENT'] ?? ''; // Write to local DB SmartHoneypotDB::insert([ 'ip' => $ip, 'form' => $this->current_form_type, 'reason' => $reason, 'uri' => $uri, 'ua' => $ua, ]); // Queue for central API SmartHoneypotAPIClient::enqueue([ 'ip' => $ip, 'form_type' => $this->current_form_type, 'reason' => $reason, 'user_agent' => $ua, 'blocked_at' => current_time('mysql'), ]); error_log("[Honeypot] {$reason} | form={$this->current_form_type} | ip={$ip} | uri={$uri}"); } /* ── Rate limiting ─────────────────────────────────────────────── */ private function check_rate_limit(): bool { $ip = $_SERVER['REMOTE_ADDR'] ?? ''; if (!$ip) { return false; } $key = 'hp_rate_' . md5($ip); $count = (int) get_transient($key); if ($count >= self::RATE_LIMIT) { $this->log_spam("Rate limit exceeded ({$count}/hr from {$ip})"); return false; } set_transient($key, $count + 1, HOUR_IN_SECONDS); return true; } /* ── WooCommerce ───────────────────────────────────────────────── */ public function validate_wc_registration($errors, $username, $password, $email) { $this->current_form_type = 'WooCommerce Registration'; if (!$this->check_submission(true)) { $errors->add('honeypot_spam', __('Error: Registration blocked. Enable JavaScript and try again.', 'smart-honeypot')); return $errors; } if (!$this->check_rate_limit()) { $errors->add('honeypot_rate', __('Error: Too many attempts. Try again later.', 'smart-honeypot')); } return $errors; } public function validate_wc_login($errors, $username, $password) { $this->current_form_type = 'WooCommerce Login'; if (!$this->check_submission(true)) { $errors->add('honeypot_spam', __('Error: Login blocked. Enable JavaScript and try again.', 'smart-honeypot')); } return $errors; } public function validate_wc_checkout($data, $errors) { $this->current_form_type = 'WooCommerce Checkout'; if (!$this->check_submission(false)) { $errors->add('honeypot_spam', __('Order could not be processed. Please try again.', 'smart-honeypot')); } } /* ── WordPress core ────────────────────────────────────────────── */ public function validate_wp_registration($errors, $login, $email) { $this->current_form_type = 'WP Registration'; if (!$this->check_submission(true)) { $errors->add('honeypot_spam', __('Error: Registration blocked. Enable JavaScript and try again.', 'smart-honeypot')); } if (!$this->check_rate_limit()) { $errors->add('honeypot_rate', __('Error: Too many attempts. Try again later.', 'smart-honeypot')); } return $errors; } public function validate_comment($commentdata) { if (is_user_logged_in() && current_user_can('moderate_comments')) { $this->clean_post_data(); return $commentdata; } $this->current_form_type = 'Comment Form'; if (!$this->check_submission(true)) { wp_die( __('Comment blocked as spam. Enable JavaScript and try again.', 'smart-honeypot'), __('Spam Detected', 'smart-honeypot'), ['response' => 403, 'back_link' => true] ); } return $commentdata; } /* ── Elementor ─────────────────────────────────────────────────── */ public function validate_elementor_form($record, $ajax_handler) { $this->current_form_type = 'Elementor Form'; if (!$this->check_submission(true)) { $ajax_handler->add_error('honeypot', __('Spam detected. Please try again.', 'smart-honeypot')); } } /* ── Generic catch-all ─────────────────────────────────────────── */ public function validate_generic_post() { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { return; } if (!isset($_POST[$this->hp_name]) && !isset($_POST[$this->token_name])) { return; } if ( isset($_POST['woocommerce-register-nonce']) || isset($_POST['woocommerce-login-nonce']) || isset($_POST['woocommerce-process-checkout-nonce']) || isset($_POST['comment_post_ID']) || (isset($_POST['action']) && $_POST['action'] === 'elementor_pro_forms_send_form') ) { return; } $this->current_form_type = 'Generic Form'; if (!$this->check_submission(false)) { if (wp_doing_ajax()) { wp_send_json_error(['message' => __('Spam detected.', 'smart-honeypot')]); } wp_safe_redirect(wp_get_referer() ?: home_url()); exit; } } /* ── CSS ───────────────────────────────────────────────────────── */ public function print_css() { echo ''; } /* ── JS — HMAC token via SubtleCrypto ──────────────────────────── */ public function print_js() { $secret = esc_js($this->secret); $token_name = esc_js($this->token_name); $time_name = esc_js($this->time_name); echo << (function(){ function computeToken(ts,secret){ var msg=ts+'|honeypot_js_proof',enc=new TextEncoder(); return crypto.subtle.importKey('raw',enc.encode(secret),{name:'HMAC',hash:'SHA-256'},false,['sign']) .then(function(k){return crypto.subtle.sign('HMAC',k,enc.encode(msg));}) .then(function(s){ var h=Array.from(new Uint8Array(s)).map(function(b){return b.toString(16).padStart(2,'0')}).join(''); return h.substring(0,16); }); } function fillTokens(){ document.querySelectorAll('input[name="{$token_name}"]').forEach(function(inp){ var p=inp.closest('div')||inp.parentElement; var tsInp=p?p.querySelector('input[name="{$time_name}"]'):null; var ts=tsInp?tsInp.value:String(Math.floor(Date.now()/1000)); computeToken(ts,'{$secret}').then(function(h){inp.value=ts+'|'+h;}); }); } if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',fillTokens);}else{fillTokens();} new MutationObserver(function(mm){ mm.forEach(function(m){m.addedNodes.forEach(function(n){ if(n.nodeType===1&&n.querySelector&&n.querySelector('input[name="{$token_name}"]'))fillTokens(); });}); }).observe(document.body,{childList:true,subtree:true}); })(); JSBLOCK; } /* ── Admin notice ──────────────────────────────────────────────── */ public function activation_notice() { if (get_transient('smart_honeypot_activated')) { echo '

Honeypot Fields is now active. All forms are protected. View logs →

'; delete_transient('smart_honeypot_activated'); } } } /* ====================================================================== * BOOT * ====================================================================*/ define('HP_PLUGIN_FILE', __FILE__); add_action('plugins_loaded', function () { if ((int) get_option(SmartHoneypotDB::TABLE_VERSION_OPTION) < SmartHoneypotDB::TABLE_VERSION) { SmartHoneypotDB::install(); } new SmartHoneypotAntiSpam(); SmartHoneypotAdmin::register(); }); // Custom cron interval (5 minutes) add_filter('cron_schedules', function ($s) { if (!isset($s['hp_5min'])) { $s['hp_5min'] = ['interval' => 300, 'display' => 'Every 5 Minutes']; } return $s; }); // Cron hooks add_action('hp_api_flush', ['SmartHoneypotAPIClient', 'flush']); add_action('hp_daily_cleanup', function () { SmartHoneypotDB::delete_older_than_days(90); }); register_activation_hook(__FILE__, function () { SmartHoneypotDB::install(); set_transient('smart_honeypot_activated', true, 30); if (!wp_next_scheduled('hp_api_flush')) wp_schedule_event(time(), 'hp_5min', 'hp_api_flush'); if (!wp_next_scheduled('hp_daily_cleanup')) wp_schedule_event(time(), 'daily', 'hp_daily_cleanup'); }); register_deactivation_hook(__FILE__, function () { delete_transient('smart_honeypot_activated'); wp_clear_scheduled_hook('hp_api_flush'); wp_clear_scheduled_hook('hp_daily_cleanup'); });