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"; if ($params) { $sql = $wpdb->prepare($sql, $params); } return $wpdb->get_results($sql) ?: []; } 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}"; if ($params) { $sql = $wpdb->prepare($sql, $params); } return (int) $wpdb->get_var($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(): int { global $wpdb; return (int) $wpdb->query("TRUNCATE TABLE " . self::table()); } public static function delete_older_than_days(int $days) { global $wpdb; $wpdb->query( $wpdb->prepare( "DELETE FROM " . self::table() . " WHERE blocked_at < DATE_SUB(NOW(), INTERVAL %d DAY)", $days ) ); } } /* ====================================================================== * 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) { $log_link = 'View Logs'; array_unshift($links, $log_link); array_push($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; } // Inline styles — no external file needed $css = ' #hp-log-wrap { max-width: 1400px; } #hp-log-wrap .hp-stats { display:flex; gap:16px; margin:16px 0; flex-wrap:wrap; } #hp-log-wrap .hp-stat-card { background:#fff; border:1px solid #c3c4c7; border-radius:4px; padding:16px 24px; min-width:140px; text-align:center; } #hp-log-wrap .hp-stat-card .hp-stat-num { font-size:2em; font-weight:700; color:#2271b1; line-height:1.2; } #hp-log-wrap .hp-stat-card .hp-stat-lbl { color:#646970; font-size:12px; } #hp-log-wrap .hp-filters { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:12px; } #hp-log-wrap .hp-filters input, #hp-log-wrap .hp-filters select { height:32px; } #hp-log-wrap table.hp-log-table { width:100%; border-collapse:collapse; background:#fff; } #hp-log-wrap table.hp-log-table th { background:#f0f0f1; padding:8px 12px; text-align:left; border-bottom:2px solid #c3c4c7; white-space:nowrap; } #hp-log-wrap table.hp-log-table td { padding:8px 12px; border-bottom:1px solid #f0f0f1; vertical-align:top; } #hp-log-wrap table.hp-log-table tr:hover td { background:#f6f7f7; } #hp-log-wrap .hp-ua { font-size:11px; color:#646970; max-width:300px; word-break:break-all; } #hp-log-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-log-wrap .hp-pagination { margin:12px 0; display:flex; align-items:center; gap:8px; } #hp-log-wrap .hp-pagination a, #hp-log-wrap .hp-pagination span { display:inline-block; padding:4px 10px; border:1px solid #c3c4c7; border-radius:3px; background:#fff; text-decoration:none; color:#2271b1; } #hp-log-wrap .hp-pagination span.current { background:#2271b1; color:#fff; border-color:#2271b1; } #hp-log-wrap .hp-clear-btn { color:#b32d2e; } '; wp_add_inline_style('common', $css); } 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, 'cleared' => 1], admin_url('admin.php'))); exit; } } public static function render_page() { if (!current_user_can('manage_options')) { return; } // Filters from query string $search = sanitize_text_field($_GET['hp_search'] ?? ''); $filter_ip = sanitize_text_field($_GET['hp_ip'] ?? ''); $filter_form = sanitize_text_field($_GET['hp_form'] ?? ''); $paged = max(1, intval($_GET['paged'] ?? 1)); $per_page = self::PER_PAGE; $offset = ($paged - 1) * $per_page; $query_args = array_filter([ 'ip' => $filter_ip, 'form' => $filter_form, 'search' => $search, 'per_page' => $per_page, 'offset' => $offset, ]); $rows = SmartHoneypotDB::get_rows($query_args); $total = SmartHoneypotDB::count($query_args); $total_ever = SmartHoneypotDB::count(); $form_types = SmartHoneypotDB::get_form_types(); $total_pages = max(1, ceil($total / $per_page)); // Unique IPs total 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_url = admin_url('admin.php?page=' . self::MENU_SLUG); ?>

Honeypot Logs

All logs have been cleared.

Total Blocked
Blocked Today
Unique IPs
Form Types Hit
Reset

Showing result (page of )

# Date / Time IP Address Form Type Reason URI User Agent
No blocked attempts recorded yet.
id) ?> blocked_at) ?> ip_address) ?>
filter   lookup ↗
form_type) ?> reason) ?> request_uri) ?> user_agent) ?>
1): ?>
1): ?> ← Prev $p]))); if ($p === $paged): ?> Next →
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 { $ts = time(); return sprintf( '', esc_attr($this->hp_name), esc_attr($this->token_name), esc_attr($this->time_name), esc_attr($ts) ); } 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 old)"); 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 DB SmartHoneypotDB::insert([ 'ip' => $ip, 'form' => $this->current_form_type, 'reason' => $reason, 'uri' => $uri, 'ua' => $ua, ]); // Also write to PHP error log for server-level monitoring 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->current_form_type .= ' (rate limited)'; $this->log_spam("Rate limit hit — {$count} attempts this hour 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 registration 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; } // Skip forms handled by specific hooks 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 () { // Run DB upgrade if needed if ((int) get_option(SmartHoneypotDB::TABLE_VERSION_OPTION) < SmartHoneypotDB::TABLE_VERSION) { SmartHoneypotDB::install(); } new SmartHoneypotAntiSpam(); SmartHoneypotAdmin::register(); }); register_activation_hook(__FILE__, function () { SmartHoneypotDB::install(); set_transient('smart_honeypot_activated', true, 30); }); register_deactivation_hook(__FILE__, function () { delete_transient('smart_honeypot_activated'); }); // Auto-prune logs older than 90 days (runs once daily) add_action('hp_daily_cleanup', function () { SmartHoneypotDB::delete_older_than_days(90); }); if (!wp_next_scheduled('hp_daily_cleanup')) { wp_schedule_event(time(), 'daily', 'hp_daily_cleanup'); }