=== Lume Traffic ===
Contributors: lume
Tags: analytics, bots, ai, crawlers, traffic
Requires at least: 5.8
Tested up to: 6.6
Requires PHP: 7.4
Stable tag: 1.3.0
License: MIT

Report your site's traffic — humans, bots, and AI agents — to Lume. After the response, non-blocking. The visitor's IP is sent only to verify crawlers and is never stored.

== Description ==

Lume Traffic reports one event per front-end request to your Lume dashboard so you can see and verify the non-human half of your traffic (crawlers, scrapers, AI agents, LLM referrals).

* **Zero added latency.** The report is sent *after* the response is flushed to the visitor (via `fastcgi_finish_request()` when available, otherwise a non-blocking request). VIP-safe.
* **Privacy-first.** Never sends cookies, query strings, or bodies — only the request line, three headers (User-Agent, Referer, Accept-Language), and the response status. The visitor's IP is sent solely to verify crawlers (reverse DNS / published ranges) and is never stored — Lume keeps only an anonymized prefix.
* **No blind trust.** Forwarded IP headers are ignored unless you configure a **Trusted proxy**, so a visitor can never choose the address Lume verifies against.
* **Zero dependencies.** Uses only the WordPress HTTP API (never a raw cURL handle). Droppable into `mu-plugins/`.
* **PHP 7.4–8.3, WordPress 5.8+.**

== Installation ==

1. In your Lume dashboard, create an **ingest token** (Settings → Tokens).
2. Upload `lume-traffic.zip` via **Plugins → Add New → Upload Plugin**, then activate. (Or drop `lume-traffic.php` into `wp-content/mu-plugins/` for a must-use install — no activation needed.)
3. Go to **Settings → Lume Traffic** and paste your ingest token. If a CDN or reverse proxy (Cloudflare, nginx, a load balancer) sits in front of WordPress, also set **Trusted proxy** to match it — crawler verification needs it. Save.
4. Visit your site; events appear on your Lume dashboard within seconds.

== Frequently Asked Questions ==

= Does a full-page cache (WP Rocket, Varnish, nginx fastcgi_cache) break it? =

Cached HTML is served without booting PHP, so cached **human** page views are not reported. Bots, uncacheable requests, logged-in users, and cache misses still boot PHP and are captured — which covers Lume's bot/agent focus well. For complete human coverage on a fully-cached site, use an edge integration (e.g. the Cloudflare Worker) instead.

= Is the visitor's IP ever sent? =

Yes — but only to verify crawlers, and it is never stored. Lume uses it transiently during verification and then discards it, persisting only an anonymized prefix — never the full IP.

Which address is sent depends on the **Trusted proxy** setting (Settings → Lume Traffic):

* **None** (the default) — the address of the machine that connected to your server. If nothing sits in front of WordPress that is the visitor, and verification works. If a forwarded header *is* present, the plugin sends no IP at all, because that header proves the connecting address belongs to a proxy rather than the visitor.
* **Cloudflare** — `CF-Connecting-IP`, accepted only when the request genuinely arrived from a Cloudflare address.
* **X-Real-IP** — the header your single reverse proxy sets.
* **X-Forwarded-For** — the entry N positions from the *end* of the list, where N is the number of trusted proxies you configure.

= Why do I have to configure a trusted proxy? Why not detect it? =

Because headers like `X-Forwarded-For` and `CF-Connecting-IP` can be sent by anybody. If the plugin trusted whichever one showed up, any visitor could claim a search engine's IP and forge a "verified" result in your dashboard — or send a bogus one and plant a false "spoofed" alert. Only you know which proxy is actually entitled to set that header, so you tell the plugin once and it trusts nothing else.

= Everything shows as "not verifiable". What do I do? =

Set **Trusted proxy** to match your hosting. Until then Lume still identifies every agent by user-agent; it simply declines to claim a crawler is authentic when it has no address it can trust. That is deliberate: behind a proxy, checking a crawler against the proxy's own address does not return "unknown", it returns a *wrong* answer, and would mark genuine search engines as impostors.

== Changelog ==

= 1.3.0 =
* **Security.** Added a **Trusted proxy** setting and stopped reading forwarded IP headers without it. Previously the plugin trusted `CF-Connecting-IP` / `X-Real-IP` / `X-Forwarded-For` from every request, so any visitor could choose the IP Lume verified against — forging a "verified" verdict, or planting a false "spoofed" alert, in your dashboard. Defaults to **None** (the direct connection only), with opt-in Cloudflare / X-Real-IP / X-Forwarded-For modes.
* Cloudflare mode accepts `CF-Connecting-IP` only when the request really came from a Cloudflare address, so the setting cannot itself be bypassed by connecting to your origin directly.
* X-Forwarded-For hops are counted from the **end** of the list (the start is supplied by the caller). If you are unsure how many proxies you have, set this **low** rather than high: too high reaches into the part of the header the visitor wrote, which is the spoofable direction, while too low only picks up one of your own proxy addresses, which is discarded.
* Malformed, missing, private or otherwise non-routable addresses now yield no IP at all instead of a truncated or internal value.
* The default (**None**) mode recognises many more proxy conventions before treating the direct connection as the visitor — Cloudflare, Fastly, Akamai, Azure, Fly, App Engine, cluster load balancers and the RFC 7239 `Forwarded` header. Each one it recognises is one more case where it declines to guess.
* Settings screen explains what the setting does and warns while verification is unconfigured.

**Upgrading is recommended for every install.** If a CDN or reverse proxy sits in front of WordPress, open Settings → Lume Traffic after updating and set Trusted proxy to match it — otherwise crawler verification will report "not verifiable".

= 1.2.0 =
* Forward the visitor's IP (transiently, never stored) so Lume can verify crawlers via reverse DNS / published IP ranges. Only an anonymized prefix is persisted.

= 1.0.0 =
* Initial release.
