=== Watrix Bot Guard ===
Contributors: watrix
Tags: bot, spam, security, rate-limit, firewall
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.5.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Log automated traffic with four kinds of rules, then rate-limit and block the offending IPs. Record first, block once you have seen the real data.

== Description ==

Pages that hold a single form – a contact page, a quote request, a download gate – get hammered by scanners and spam bots. The first real damage is to your analytics: page views are inflated and you can no longer read what visitors actually do.

Watrix Bot Guard catches the source of that traffic with **four kinds of rules** and lets you decide how to deal with it:

* **Path flood** – the same IP hitting a specific path (e.g. `/contact/`) too many times in a short window
* **404 burst** – vulnerability scanners walking through hundreds of non-existent URLs
* **Trap URL** – a hidden link that is disallowed in `robots.txt`; only bots that ignore it will ever follow it
* **Bad User-Agent** – empty user agents and signatures of scanners, headless browsers and HTTP libraries

Rules can be added, edited and disabled individually, each with its own paths, threshold, action and block duration.

= Record first, block later =

Right after activation the plugin runs in **log-only mode**: every rule records what it sees and nobody is blocked. Look at the dashboard after a few days, see whether the traffic comes from a handful of IPs or is spread out, and only then switch to **enforce mode** – or take the generated `.htaccess` / nginx snippet and block those IPs in front of PHP.

= Reduce what you expose =

A separate screen, **Bot Guard → Hardening**, turns down the amount of information your site hands out to anyone who asks. Every switch there is **off by default** and can be turned on one at a time.

* **REST user list** – out of the box WordPress answers `/wp-json/wp/v2/users` to anyone, and the `slug` it returns is usually the login name. Turning this on returns 401/403 to visitors without the `list_users` capability, while logged-in editors, the block editor and application-password integrations keep working exactly as before. The oEmbed endpoint keeps answering, because other sites rely on it to embed your posts – only the `author_name` and `author_url` fields, which carry the same slug, are dropped. The rest of the REST API is untouched – the WooCommerce Store API, contact form plugins and the block editor all keep their endpoints.
* **Security headers** – `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `Permissions-Policy`, `Strict-Transport-Security` and `Content-Security-Policy`, each with its own switch. A header another plugin has already sent from PHP is never sent twice. Headers added by nginx or Apache are invisible to PHP, so check the real response once after switching something on. HSTS is only ever sent over HTTPS, and `includeSubDomains` / `preload` stay off unless you deliberately ask for them. CSP has no default value at all: it ships empty, front-end only, and can be tried as `Content-Security-Policy-Report-Only` first.
* **Version disclosure** – removes the `<meta name="generator">` tags emitted by WordPress core, WooCommerce and Site Kit by Google, in HTML and in the feeds.

Hiding a version number does not make an old version safe. The screen says so, plainly: the real fix is keeping WordPress, themes and plugins updated, and this is the supporting act.

= What else is included =

* Verified crawler exclusion – Googlebot, Bingbot, Applebot and others are let through only after a reverse-then-forward DNS check; a crawler that claims to be Googlebot but fails the check is treated as a fake and blocked
* Allow list with CIDR and IPv6 support for your own office and your client's office
* Optional grouping by subnet (/24 for IPv4, /64 for IPv6)
* Early blocking – already-blocked IPs are stopped on `init`, before the main query runs
* Dashboard with per-rule, per-IP, per-path and per-user-agent breakdowns of the last 7 days
* Access log with filters and CSV export
* Manual block list
* Server-side snippets: Apache `.htaccess`, nginx `deny`, and a plain IP list for your analytics tool's internal-traffic filter
* Settings export / import as JSON to roll the same configuration out to other sites
* WP-CLI: `wp bot-guard top | blocks | block | unblock | mode | export | settings | cleanup`
* Optional e-mail / webhook notification when a new IP is auto-blocked (throttled to one per hour)
* Daily cleanup of expired blocks and of log rows older than the retention period

= What it deliberately does not do =

* Login protection and two-factor authentication – there are dedicated plugins for that
* Country blocking – it would require bundling a GeoIP database

= Privacy =

The plugin stores the IP address, request path, user agent and referrer of requests that match a rule, in your own database, for the retention period you set (30 days by default). Nothing is sent to WATRIX or to any third party. If you configure a webhook URL, block notifications are sent to that URL and nowhere else. An optional "anonymize IP" setting masks the last octet before storing.

The verified-crawler check performs DNS lookups (reverse and forward) against the visitor's IP. Results are cached for 12 hours.

== Installation ==

1. Upload the plugin folder to `/wp-content/plugins/` or install it from the Plugins screen.
2. Activate it.
3. Go to **Bot Guard → Rules** and adjust the target path of the "path flood" rule to match your site (the default is `/contact/`).
4. Leave the mode on **log only** for a few days.
5. Check **Bot Guard → Overview**. If the traffic is concentrated on a few IPs, copy the snippet from **Server-side blocking** into your server configuration. If it is spread out, switch the mode to **enforce** in Settings.

== Frequently Asked Questions ==

= Will it block real visitors? =

Not in log-only mode – nothing is blocked at all. In enforce mode the default thresholds (20 hits on one page within 60 minutes, 30 404s within 10 minutes) are far beyond what a human does. Logged-in users and verified search-engine crawlers are excluded, and you can add your own IP ranges to the allow list. If your monitoring service fetches your site with `curl` or a similar tool, add its IP to the allow list or remove that signature from the bad user-agent list.

= Which IP address does it use? =

`REMOTE_ADDR` by default. Only switch to `CF-Connecting-IP` or `X-Forwarded-For` if your site actually sits behind Cloudflare or another reverse proxy – those headers can be forged by clients otherwise.

= Does it reduce server load? =

Blocking happens inside WordPress, so a blocked request still reaches PHP (but stops on `init`, before the query). To keep the load off entirely, paste the generated `.htaccess` or nginx snippet into your server configuration.

= Does it work with page caching? =

Rule evaluation runs on uncached requests only. Cached pages served by a caching plugin or CDN are never counted, so the plugin is most effective on pages that are not cached, such as forms.

= Can I use it in Japanese? =

The admin interface is currently written in Japanese and is fully translatable through the `watrix-bot-guard` text domain.

== Screenshots ==

1. Overview: per-rule, per-IP, per-path and per-user-agent breakdown of the last 7 days. / 概要画面。ルール別・IP別・パス別・User-Agent別の集計。
2. Rules: the four built-in rules, each with its own paths, threshold and action. / ルール画面。4種の標準ルール。
3. Access log: filter by IP, by rule, or by whether the request was blocked. / アクセスログ。IP・ルール・遮断有無で絞り込み。
4. Server-side blocking: ready-to-paste .htaccess and nginx snippets for the blocked IPs. / サーバー側で遮断。.htaccess / nginx 用スニペット。
5. Settings: mode switch, exclusions, bad user-agent signatures, trap URL, notifications. / 設定画面。モード・除外・署名・罠URL・通知。

== Changelog ==

= 1.5.0 =
* New screen **Hardening**, which reduces what the site exposes. Every option on it is off by default, so updating to this version changes nothing until you switch something on.
* REST user list: `/wp/v2/users` and `/wp/v2/users/<id>` can now be closed to anyone without the `list_users` capability. The endpoints stay in the route index and keep answering logged-in users with the capability, so the block editor's author picker and application-password integrations are unaffected. `/wp/v2/users/me` is left alone, and so is a user reading their own record by id, so a theme or block that looks an author up by id keeps working. Route matching is case-insensitive, the same way WordPress itself matches routes, so `/wp/v2/Users` cannot be used to walk around it. The oEmbed endpoint keeps working - it is how other sites embed your posts - but its `author_name` and `author_url` fields, which leak the same slug, are dropped for visitors without the capability. One visible side effect: fetching posts with `_embed` returns an error object in place of `_embedded.author`; the posts themselves are unaffected. No other namespace is touched - the WooCommerce Store API, contact form plugins and the block editor keep working, which is not the case with the blunt "disable the REST API" switch other plugins offer.
* Security headers: `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `Permissions-Policy`, `Strict-Transport-Security` and `Content-Security-Policy`, each behind its own switch. Headers already sent from PHP by another plugin are not sent a second time; headers added by the web server itself are invisible to PHP and can still end up duplicated, so the screen tells you to check the real response once. HSTS is suppressed entirely unless the site is served over HTTPS, and `includeSubDomains` and `preload` are off by default with a warning next to them. CSP ships empty, applies to the front end only and can be run as `Content-Security-Policy-Report-Only`.
* Version disclosure: an option to drop the `<meta name="generator">` tags from WordPress core, WooCommerce and Site Kit by Google, in HTML and in the feeds. The `?ver=` query string on CSS and JS is deliberately left in place, because removing it breaks cache busting.
* No change to how rules are evaluated or how requests are blocked.

= 1.4.2 =
* All user-facing strings in the admin screens, WP-CLI commands, notifications and generated server snippets are now translatable. Added the `Domain Path` header and a `languages/watrix-bot-guard.pot` template so the plugin can be translated on translate.wordpress.org.
* No change to how rules are evaluated or how requests are blocked.

= 1.4.1 =
* Removed the "Tested up to" header from the main plugin file. It now lives only in readme.txt, which is the supported place for it.
* Rule path patterns, signatures and other multi-line settings are sanitized line by line with sanitize_text_field() before they are stored.
* Fixed the Plugin URI, which pointed at a page that did not exist.

= 1.4.0 =
* AI assistants are no longer blocked. When someone asks ChatGPT, Claude or Perplexity a question and the answer cites your site, that visit is a referral - not an attack. Requests from ChatGPT-User, OAI-SearchBot, GPTBot, Claude-User, Claude-SearchBot, ClaudeBot, Perplexity-User and PerplexityBot are now exempt.
* Verification uses the source IP ranges each provider publishes, not the User-Agent alone, so the exemption cannot be claimed by spoofing a name. The lists refresh once a day. If a list cannot be fetched, the request is allowed through rather than blocked, so a network failure never costs you a referral.
* New setting under Settings > Exclusions to turn the exemption off, with a table showing which ranges have been fetched.
* New command `wp bot-guard ai` to show the status, `--refresh` to fetch now, and `--check=<ip> --ua=<user-agent>` to test a single request.
* Fixed: percent-encoded paths were mangled in the log and in path rules. A URL such as `/product/%E6%A5%B5hepa/` was recorded as `/product/-/`, so non-ASCII slugs could not be logged or matched correctly. Paths are now decoded before they are stored and compared.

= 1.3.1 =
* Fixed a fatal error that could take the whole site down when a class file was missing (for example during a partial update). The plugin now checks its own files first and disables only itself, showing an admin notice instead.

= 1.3.0 =
* First release submitted to the WordPress.org plugin directory.
* Plugin headers, LICENSE and file layout aligned with the other Watrix plugins.

= 1.2.0 =
* Prefix changed from `wag_` to `wxag_` to comply with the WordPress.org guidelines. Existing options and tables are migrated automatically.
* readme.txt and plugin header prepared for the WordPress.org directory.

= 1.1.0 =
* Redesigned around rules: path flood, 404 burst, trap URL, bad user-agent.
* Fake-crawler blocking, subnet grouping, early blocking on `init`.
* Server-side snippets, settings export/import, WP-CLI commands.

= 1.0.0 =
* Initial release: log and rate-limit repeated hits on specific paths.

== Upgrade Notice ==

= 1.5.0 =
Adds a Hardening screen: close the REST user list, send security headers, drop the generator meta tag. Everything on it is off by default, so the update on its own changes nothing.

= 1.3.0 =
Metadata and packaging only. No change to how rules are evaluated.

= 1.2.0 =
Internal prefix change. Your settings, log and block list are migrated automatically on the first page load after the update.
