{"id":374775,"date":"2026-09-25T14:01:19","date_gmt":"2026-09-25T14:01:19","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/botsniffer\/"},"modified":"2026-09-25T14:01:10","modified_gmt":"2026-09-25T14:01:10","slug":"sm-bot-flagger","status":"publish","type":"plugin","link":"https:\/\/hsb.wordpress.org\/plugins\/sm-bot-flagger\/","author":23572316,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.0.0","stable_tag":"1.0.0","tested":"7.1.2","requires":"6.0","requires_php":"7.4","requires_plugins":null,"header_name":"SEOmarketeer Bot Flagger","header_author":"SEOmarketeer","header_description":"Logs every request to your site and tells you what it really is: a person, a search engine, an AI crawler, your own cache, or an attack. Includes a built-in MCP server.","assets_banners_color":"13233f","last_updated":"2026-09-25 14:01:10","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"","header_author_uri":"https:\/\/seomarketeer.eu","rating":0,"author_block_rating":0,"active_installs":0,"downloads":45,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.0":{"tag":"1.0.0","author":"seomarketeer","date":"2026-09-25 14:01:10","revision":3713153}},"upgrade_notice":[],"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3713153,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3713153,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256},"icon.svg":{"filename":"icon.svg","revision":3713153,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3713153,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3713153,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.0.0"],"block_files":[],"assets_screenshots":[],"screenshots":{"1":"The dashboard: who is visiting, over time and by category.","2":"The traffic log with the chart grouped by user agent.","3":"Correcting a classification from a log row.","4":"The classification rules.","5":"The MCP endpoint with a token and client configuration.","6":"Settings, including the behaviour rules and privacy options."}},"plugin_section":[],"plugin_tags":[246479,4866,242115,600,493],"plugin_category":[54,55],"plugin_contributors":[282721],"plugin_business_model":[],"class_list":["post-374775","plugin","type-plugin","status-publish","hentry","plugin_tags-ai-crawlers","plugin_tags-bots","plugin_tags-mcp","plugin_tags-security","plugin_tags-traffic","plugin_category-security-and-spam-protection","plugin_category-seo-and-marketing","plugin_contributors-seomarketeer","plugin_committers-seomarketeer"],"banners":{"banner":"https:\/\/ps.w.org\/sm-bot-flagger\/assets\/banner-772x250.png?rev=3713153","banner_2x":"https:\/\/ps.w.org\/sm-bot-flagger\/assets\/banner-1544x500.png?rev=3713153","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/sm-bot-flagger\/assets\/icon.svg?rev=3713153","icon":"https:\/\/ps.w.org\/sm-bot-flagger\/assets\/icon.svg?rev=3713153","icon_2x":false,"generated":false},"screenshots":[],"raw_content":"<!--section=description-->\n<p>Most analytics trust what a visitor says it is. Attack tools know that, so they call themselves Chrome, or Googlebot, or an AI crawler, and walk straight past. SM Bot Flagger checks what they actually do: a request for your cloud credentials is an attack, whatever name it gives.<\/p>\n\n<h4>What it does<\/h4>\n\n<p>Each front-end request is written to its own table with:<\/p>\n\n<ul>\n<li>date and time, in site time and UTC<\/li>\n<li>IP address, stored whole or masked<\/li>\n<li>user agent<\/li>\n<li>referrer and referring domain<\/li>\n<li>requested path and query string<\/li>\n<li>HTTP status code<\/li>\n<li>whether the request started a new visit, which makes it a landing page<\/li>\n<li>the category and client name the rules assigned<\/li>\n<li>how long WordPress took to build the page<\/li>\n<\/ul>\n\n<p>Nothing is written to postmeta, and there is one INSERT per request.<\/p>\n\n<h4>Categories<\/h4>\n\n<p>Traffic is sorted into six buckets:<\/p>\n\n<ul>\n<li>Regular traffic \u2014 looks like a person in a browser<\/li>\n<li>Search bots \u2014 Googlebot, Bingbot, DuckDuckBot and friends<\/li>\n<li>AI bots \u2014 GPTBot, ClaudeBot, PerplexityBot, Bytespider, CCBot and around thirty others<\/li>\n<li>Malicious traffic \u2014 sqlmap, Nikto, WPScan, plus around 75 path signatures:\nconfig and env files, backup leftovers, SSH and private keys, AWS, Azure,\nGoogle Cloud, Oracle, OVH, Linode and Hetzner credentials, Docker and\nKubernetes, Terraform state, Vite and Spring Actuator file disclosure, path\ntraversal and SQL injection, and credential files for AI coding agents<\/li>\n<li>Other bots \u2014 SEO crawlers, uptime monitors, link previews, HTTP libraries<\/li>\n<li>Caching \u2014 your own cache plugins warming pages: WP Rocket preload and bot,\nWP Fastest Cache, LiteSpeed crawler, W3 Total Cache, WP Super Cache,\nFlyingPress, NitroPack, Cache Enabler, Swift Performance, Cloudflare Always\nOnline<\/li>\n<li>Unclassified \u2014 empty or unrecognised user agents<\/li>\n<\/ul>\n\n<p>Cache warming is the site requesting its own pages, so it is not a visit and it\nis not really a bot either. Keeping it separate stops preload runs from inflating\nthe traffic numbers. Filter the log to Caching to check a preload actually ran.<\/p>\n\n<p>Settings has a \"Do not log\" row where you can tick categories to discard. Those\nrequests are still classified, then dropped before anything is written, so they\ncost one pass over the rules and no database queries at all. This is worth doing\nfor Caching on a busy site, where a preload run can easily be the largest source\nof requests. Nothing is stored, so skipped traffic cannot be recovered later and\nwill not show up in the log, the charts or the API.<\/p>\n\n<p>A rule matches on one of three things: the user agent, the request path, or the\nIP address. User agent and path patterns match anywhere in the value, with *\nas a wildcard and an optional regular expression. Address patterns understand an\nexact address, a prefix such as 203.0.113, a wildcard such as 203.0.113.*, and\nCIDR ranges such as 203.0.113.0\/24 or 2001:db8::\/32. Prefixes only match on a\nboundary, so 192.168.1 will not match 192.168.10.4.<\/p>\n\n<p>This is how you handle a crawler that lies about its user agent, or a scraper\nrunning from one hosting range: match the address instead. It is also how you\nmark an internal monitoring path or a staging endpoint as something other than\nreal traffic.<\/p>\n\n<p>Rules live under SM Bot Flagger \u2192 Classification. Each rule matches a substring\nor a regular expression against the user agent, the path or the IP. They are\nevaluated top to bottom and the first match wins, so put narrow rules above broad\nones. You can edit, reorder, disable, delete and restore them.<\/p>\n\n<h4>Privacy<\/h4>\n\n<p>SM Bot Flagger processes personal data in the sense of the GDPR, because an IP\naddress is personal data. The defaults are chosen to keep as little as\npossible:<\/p>\n\n<ul>\n<li>IP addresses are masked before storage, to \/24 for IPv4 and \/48 for IPv6.\nFull addresses are stored only if you switch that on.<\/li>\n<li>Visitors are counted through a hash of the masked address, the user agent\nand a secret that changes every day, so nobody can be followed from one day\nto the next.<\/li>\n<li>Values that look like secrets in URLs, such as password reset keys and\ntokens, are removed before a row is written.<\/li>\n<li>Administrators are not logged by default.<\/li>\n<li>Entries are deleted after 90 days by default.<\/li>\n<li>Nothing leaves your server. There is no tracking script, no external\nservice, no call home. The plugin author never sees your data.<\/li>\n<\/ul>\n\n<p>The plugin adds suggested text to your site's privacy policy guide under\nSettings &gt; Privacy &gt; Policy Guide. Adapt it to your configuration. If you\nstore full addresses or keep entries longer, say so there, and check your\nlegal basis: for most sites that is a legitimate interest in security, which\nshould be recorded in your processing register.<\/p>\n\n<p>The optional MCP endpoint lets a tool you connect read the log. Whoever holds\na token can read the same data you can, so treat tokens as passwords and\nrevoke the ones you no longer use.<\/p>\n\n<h4>The MCP endpoint<\/h4>\n\n<p>Enable it under Settings, then create a token under MCP API.<\/p>\n\n<p>Endpoint: <code>https:\/\/your-site.tld\/wp-json\/sm-bot-flagger\/v1\/mcp<\/code>\nTransport: streamable HTTP, JSON-RPC 2.0 over POST\nAuth: <code>Authorization: Bearer &lt;token&gt;<\/code>, or <code>X-MCP-Token: &lt;token&gt;<\/code> if a proxy\nstrips the Authorization header<\/p>\n\n<p>Tools:<\/p>\n\n<ul>\n<li><code>get_traffic_summary<\/code> \u2014 totals and the category split for a period<\/li>\n<li><code>get_timeseries<\/code> \u2014 hourly or daily counts per category<\/li>\n<li><code>get_top<\/code> \u2014 ranks pages, landing pages, referrers, referring domains, user\nagents, clients, categories, status codes, IPs, rules or hour of day<\/li>\n<li><code>search_hits<\/code> \u2014 individual requests, with filters<\/li>\n<li><code>classify_user_agent<\/code> \u2014 test a user agent against the current rules<\/li>\n<li><code>list_classification_rules<\/code> \u2014 the rules in evaluation order<\/li>\n<li><code>upsert_classification_rule<\/code> \u2014 add or change a rule, write token only<\/li>\n<li><code>delete_classification_rule<\/code> \u2014 remove a rule, write token only<\/li>\n<\/ul>\n\n<p>Read tokens can only read. Give a token write access only if you want the client\nto change how traffic is categorised.<\/p>\n\n<p>If Authorization never arrives, add this to .htaccess on Apache:<\/p>\n\n<pre><code>SetEnvIf Authorization \"(.*)\" HTTP_AUTHORIZATION=$1\n<\/code><\/pre>\n\n<h4>Hooks<\/h4>\n\n<p>Extension points for add-ons:<\/p>\n\n<pre><code>sm_bot_flagger_categories (array) \u2014 add your own traffic categories. They appear in the\n<\/code><\/pre>\n\n<p>rules editor, the charts, the filters and the MCP schemas automatically.\n    sm_bot_flagger_classification (array) \u2014 override the category, label or rule for a\nrequest, after the rules have run.\n    sm_bot_flagger_mcp_tools (array) \u2014 register MCP tools. Include a <code>callback<\/code> key and the\nfree plugin will dispatch to it.\n    sm_bot_flagger_stats_where (array) \u2014 append conditions to every stats query.\n    sm_bot_flagger_admin_menu (action) \u2014 add a submenu page under SM Bot Flagger.\n    sm_bot_flagger_dashboard_cards \/ <code>sm_bot_flagger_dashboard_panels<\/code> (actions) \u2014 add to the dashboard.\n    sm_bot_flagger_log_columns (array) and <code>sm_bot_flagger_log_column<\/code> (action) \u2014 add columns to the log.\n    sm_bot_flagger_settings_fields (action) and <code>sm_bot_flagger_sanitize_settings<\/code> (array) \u2014 add settings.<\/p>\n\n<pre><code>SM_Bot_Flagger_Logger::current_classification() classifies the current request once and\n<\/code><\/pre>\n\n<p>caches it, so an add-on can act on a crawler early rather than at shutdown.<\/p>\n\n<pre><code>sm_bot_flagger_should_log (bool) \u2014 return false to skip logging a request.\nsm_bot_flagger_hit_data (array) \u2014 change the row before it is written, or return an empty\n<\/code><\/pre>\n\n<p>array to drop it. Useful for adding a country lookup.\n    sm_bot_flagger_mcp_tools (array) \u2014 add your own MCP tools. Give each one a handler named\n    tool_ on a class of your choosing and hook <code>tools\/call<\/code> accordingly.<\/p>\n\n<h4>Notes on accuracy<\/h4>\n\n<ul>\n<li>Logging happens on <code>shutdown<\/code>, so requests served entirely from a page cache\nor a CDN never reach PHP and are not counted. On a cached site, treat these\nnumbers as a lower bound.<\/li>\n<li>Bot detection relies on self-reported user agents. Anything determined to look\nlike a browser will be counted as regular traffic.<\/li>\n<li>Only pick a proxy header for the IP if a proxy actually sets it. A client can\nsend X-Forwarded-For itself, which would let anyone forge their address.<\/li>\n<\/ul>\n\n<h4>Security notes<\/h4>\n\n<ul>\n<li>Query string values for parameters like key, token, code, password and nonce\nare replaced with [redacted] before a row is written, so password reset links\nand OAuth callbacks do not end up sitting in the log table.<\/li>\n<li>CSV exports prefix any cell starting with =, +, - or @ with an apostrophe, so\na crafted user agent cannot run as a formula when the file is opened.<\/li>\n<li>The MCP endpoint blocks an address for 15 minutes after 20 failed token\nattempts.<\/li>\n<li>Tokens are not accepted as a ?token= query parameter by default, because\nquery strings leak into access logs and referrers. Enable it with the\n  sm_bot_flagger_mcp_allow_query_token filter if a client cannot send headers.<\/li>\n<li>A regular expression that does not compile is switched off rather than being\ntreated as literal text, so a typo cannot silently widen what a rule matches.<\/li>\n<li>Rule patterns are matched against at most the first 1000 characters of a\nheader, which bounds the cost of a badly written expression.<\/li>\n<\/ul>\n\n<p>A write-scoped MCP token can change classification rules, and those rules run on\nevery front-end request. Treat a write token as trusted, and hand out read\ntokens for anything that only needs to look at the numbers.<\/p>\n\n<h4>Behaviour rules<\/h4>\n\n<p>Pattern rules judge a request on what it looks like. Behaviour rules judge a\nclient on what it has been doing. They run at log time, because they need the\nresponse status and a short memory, and they need a stored IP address, so they\ndo nothing when IP storage is off. Confirmed search and AI crawlers are never\ndemoted by them.<\/p>\n\n<p>Random paths. A 404 on a single path segment of 4 to 12 alphanumeric characters\nwith no vowels and mixed case, such as \/6bXX29bt, is treated as scanning. Real\nslugs contain vowels; generated tokens do not. The 404 is required, so a short\nconsonant heavy page of your own is never caught.<\/p>\n\n<p>Repeated 404s. Flagged on both a count and a share: by default 8 misses within\n10 minutes, and at least half of that client's requests in the window missing.\nThe share is what makes this safe. A busy legitimate client can collect a\nhandful of 404s among hundreds of good requests, and counting alone would flag\nit. Scanners miss on almost everything they ask for.<\/p>\n\n<p>Login attempts. A cumulative count of requests to wp-login.php and xmlrpc.php,\n10 per hour by default. Deliberately not a rate: credential stuffing is usually\nslow and spread over many addresses, so a per-second limit never sees it.<\/p>\n\n<p>Request rate, off by default. On real traffic the fastest clients are usually\nlegitimate, while scanners and credential stuffing run slowly enough to pass any\nthreshold worth setting. Counting 404s separates the two far better than\ncounting requests. Measure your own traffic before switching this on.<\/p>\n\n<p>Once a client trips a rule it stays flagged for an hour by default, and what it\nalready did in that window is recategorised too.<\/p>\n\n<h4>Path precedence<\/h4>\n\n<p>A user agent is a claim the client makes about itself. Anything can send\n\"OAI-SearchBot\" or \"Googlebot\", and plenty of attack tooling does, because it\ngets past naive filtering.<\/p>\n\n<p>Settings has \"Trust the path\", on by default, which tests path and address rules\nbefore user agent rules. A request for \/root\/.aws\/credentials is then recorded\nas an attack whatever it claims to be, and your genuine crawler rules stay\ncorrect for genuine crawlers.<\/p>\n\n<p>Turn it off if you would rather rules were evaluated strictly in the order shown\non the Classification screen.<\/p>\n\n<h4>Filtering<\/h4>\n\n<p>Dashboard tiles toggle categories, and several can be combined. Rows in the top\nlists are links: click a page, landing page, status code, client or referring\ndomain to open the traffic log filtered to it.<\/p>\n\n<p>The log itself filters on category, status family (2xx to 5xx), an exact status\ncode, a page path, landing pages only, and a free text search across path, user\nagent, referrer, IP and client. Combining a path with \"landing pages only\" gives\nyou the entry traffic for one page. Filters survive paging and are applied to\nCSV exports.<\/p>\n\n<h4>Correcting a classification<\/h4>\n\n<p>Open the Traffic log and click the client name on any row. You get the user\nagent as an editable pattern, a name, and a category. Shorten the pattern to the\ndistinctive part before saving, for example <code>MyScraper\/2.<\/code> rather than the whole\nstring, so the rule catches other versions too.<\/p>\n\n<p>Saving puts the rule first in the evaluation order, so it beats the shipped\ndefaults. Tick the box to also rewrite requests that are already in the log,\nwhich is matched on the user agent containing your pattern.<\/p>\n\n<p>The chart above the table follows the filters, and can be grouped by category,\nby client or by the raw user agent. The eight busiest values get their own band\nand the rest are folded into \"Everything else\". \"Show full user agent\" prints\nthe whole string under each client name.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Install through Plugins &gt; Add New, or upload the zip.<\/li>\n<li>Activate. Logging starts straight away with privacy-friendly defaults.<\/li>\n<li>Open SM Bot Flagger in the admin menu. The dashboard fills as traffic arrives.<\/li>\n<li>Optional: under Settings, turn on the MCP endpoint and create a token to\nconnect Claude or another MCP client.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"will%20it%20slow%20my%20site%20down%3F\"><h3>Will it slow my site down?<\/h3><\/dt>\n<dd><p>Logging happens at the very end of each request, as one database write.\nBehaviour rules only keep counters for clients that have already requested a\nmissing page.<\/p><\/dd>\n<dt id=\"does%20it%20work%20with%20page%20caching%3F\"><h3>Does it work with page caching?<\/h3><\/dt>\n<dd><p>Pages served straight from a cache never reach WordPress, so they are not\nlogged. On a heavily cached site, read the numbers as a floor rather than a\ntotal. Your cache plugin's own warming requests are recognised and kept out\nof your visitor numbers.<\/p><\/dd>\n<dt id=\"is%20it%20gdpr%20compliant%3F\"><h3>Is it GDPR compliant?<\/h3><\/dt>\n<dd><p>It is built to keep as little as possible: masked addresses, a visitor hash\nthat changes daily, secrets removed from URLs, administrators excluded, and\n90-day retention. See the Privacy section. Compliance also depends on how you\nrun your site: adapt the suggested privacy policy text, and record your legal\nbasis.<\/p><\/dd>\n<dt id=\"does%20any%20data%20leave%20my%20server%3F\"><h3>Does any data leave my server?<\/h3><\/dt>\n<dd><p>No. There is no tracking script, no external service and no call home. The\nplugin author never sees your traffic.<\/p><\/dd>\n<dt id=\"what%20is%20the%20mcp%20endpoint%3F\"><h3>What is the MCP endpoint?<\/h3><\/dt>\n<dd><p>A small server inside WordPress that lets an AI assistant or any MCP client\nread the traffic log and ask questions in plain language. It is off by\ndefault, needs a token you create, and read-only tokens cannot change\nanything.<\/p><\/dd>\n<dt id=\"can%20i%20correct%20a%20classification%3F\"><h3>Can I correct a classification?<\/h3><\/dt>\n<dd><p>Yes. Click a client in the traffic log, choose the category, and optionally\nmatch on the path or address instead of the user agent. Past requests are\ncorrected too.<\/p><\/dd>\n<dt id=\"is%20there%20a%20paid%20version%3F\"><h3>Is there a paid version?<\/h3><\/dt>\n<dd><p>Yes. The Pro add-on adds verified crawlers, so a fake Googlebot is caught by\nDNS rather than by name; blocking by category, behaviour or address, with a\nmonitor mode; and priority support. See https:\/\/seomarketeer.eu.<\/p><\/dd>\n<dt id=\"where%20do%20i%20get%20help%3F\"><h3>Where do I get help?<\/h3><\/dt>\n<dd><p>In this plugin's support forum on WordPress.org, or through\nhttps:\/\/seomarketeer.eu.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>First public release.<\/li>\n<\/ul>","raw_excerpt":"Logs every request and tells you what it really is: a person, a search engine, an AI crawler, your own cache, or an attack.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/hsb.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/374775","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hsb.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/hsb.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/hsb.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=374775"}],"author":[{"embeddable":true,"href":"https:\/\/hsb.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/seomarketeer"}],"wp:attachment":[{"href":"https:\/\/hsb.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=374775"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/hsb.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=374775"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/hsb.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=374775"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/hsb.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=374775"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/hsb.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=374775"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/hsb.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=374775"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}