=== Vulnity Security ===
Contributors: manuelgalan
Requires at least: 6.2
Tested up to: 6.9
Requires PHP: 7.4
Stable tag: 1.4.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Tags: security, siem, monitoring, intrusion-detection

Security monitoring and SIEM integration that keeps your WordPress sites safe in real time.

== Description ==
Vulnity Security brings enterprise-grade threat detection to WordPress. It connects your site to Vulnity's SIEM platform, correlates events, and alerts you before issues become incidents.

= Features =
* Real-time security event collection and forwarding to Vulnity SIEM.
* Dashboard widgets that highlight critical findings and remediation steps.
* Scheduled security scans for core files, plugins, and themes.
* Centralized logging compatible with major SOC workflows.

= Integration Requirements =
To receive alerts, configure an API token and endpoint URL provided by your Vulnity SIEM account. Detailed configuration instructions are displayed after activating the plugin under **Vulnity \> Settings**.

WordPress Multisite is not supported in Vulnity Security 1.4.0. Network activation and subsite activation are blocked to avoid mixing configuration, firewall storage, cron jobs, or uninstall cleanup between subsites.

Vulnity Security 1.4.0 requires WordPress 6.2 or newer. Older WordPress versions are blocked fail-safe so the plugin stays inactive instead of registering security, firewall, cron, REST, or SIEM behavior on an unsupported runtime.

= External Services =
This plugin connects to Vulnity's external API hosted on Supabase Edge Functions (domain: `euxnoekqasvzwfcbybkg.supabase.co`, base URL `https://euxnoekqasvzwfcbybkg.supabase.co/functions/v1`) to power SIEM alerts, inventory sync, and mitigation updates.

* **What the service is and what it is used for:**
  * Vulnity SIEM API for pairing/unpairing, heartbeat checks, sending alerts, testing connectivity, syncing inventory, and receiving mitigation policies.
* **Endpoints used:**
  * `/pair-plugin`, `/unpair-plugin` (pairing and disconnecting the site).
  * `/heartbeat` (periodic health check).
  * `/connection-test` (manual connection test).
  * `/scan-site-info` (inventory sync).
  * `/generic-alert`, `/brute-force-alert`, `/file-security-alert`, `/manage-user`, `/user-management-alert`, `/permission-change-alert`, `/file-editor-alert`, `/plugin-change-alert`, `/theme-change-alert`, `/core-update-alert`, `/suspicious-query-alert`, `/scanner-detected-alert` (security alerts).
  * `/mitigation-config`, `/mitigation-update` (mitigation policy sync and block/unblock updates).
  * `/delegated-login-callback` (audit confirmation of a delegated wp-admin login, signed with the reinforced v2 scheme).
* **Assisted connection (linking token):**
  * From the Vulnity panel you can start an assisted connection instead of copying a Site ID and Pairing Code. The panel issues a single-use, short-lived (15 minute) linking token and hands it to this site with a server-side POST to wp-admin; the plugin stores it in a transient tied to your user account and renders a confirmation card that names the organization and the registered domain. The bearer is never placed in the URL, the fragment, the DOM, the page HTML or any browser JavaScript. Confirming requires a valid nonce **and** re-authentication with your current WordPress password; only then does the plugin exchange the token for the site credentials against `/pair-plugin` over HTTPS, server-side. The exchange only succeeds when this site's URL matches the domain registered in the panel, the pending token is discarded after three failed confirmations, and redirects are never followed for these requests.
* **What data is sent and when:**
  * Pairing/unpairing: site ID and pair code (manual mode) or the single-use linking token (assisted mode), the site URL and home URL, plugin/WordPress/PHP versions, and timestamp when pairing or disconnecting occurs.
  * Heartbeat: site ID, URLs, site metadata (name, language, timezone, theme), and runtime info (plugin/WordPress/PHP versions, latency) on a scheduled interval.
  * Alerts: site ID, alert type/severity, timestamps, and event details (such as IP address, user/action metadata, or file change context) whenever a security event is detected.
  * Inventory sync: site inventory details (installed plugins/themes/core metadata) when inventory sync runs.
  * Mitigation: site ID, block/unblock actions, IP address, reason, duration, and rule metadata when mitigation rules are synced or enforcement actions occur.
* **Why the data is sent:**
  * To associate the site with your Vulnity account, deliver security alerts to the SIEM, validate connectivity, synchronize inventory and mitigation policies, and keep firewall enforcement consistent.
* **Policies:** See the Vulnity [Terms of Service](https://vulnity.io/terms) and [Privacy Policy](https://vulnity.io/privacy) for details on how data is handled.

== Installation ==
1. Upload the plugin files to the `/wp-content/plugins/vulnity` directory or install from the WordPress plugin repository.
2. Activate the plugin through the **Plugins** screen in WordPress.
3. Navigate to **Vulnity \> Settings**, enter your Vulnity SIEM credentials, and save.
4. (Optional) Enable scheduled scans on the **Monitoring** tab to receive weekly reports.

== Frequently Asked Questions ==

= Do I need a Vulnity SIEM subscription? =
Yes. The plugin requires an active Vulnity SIEM account to collect and analyze events.

= Will the plugin slow down my site? =
No. Event collection runs asynchronously and offloads processing to the Vulnity cloud platform.

= Can I disable certain alerts? =
Absolutely. Use the **Alert Policies** section within the plugin settings to mute or reclassify events.

= How does delegated login decide a request really came over HTTPS? (trust model) =
The `/sso` gate and the plugin's HTTPS shim only believe forwarded headers (`X-Forwarded-Proto`, `X-Forwarded-SSL`) when the immediate network hop (`REMOTE_ADDR`) is a trusted proxy. Trusted means: an entry you configured in the `vulnity_trusted_proxies` option (IPs or CIDRs — recommended, keep it to the minimal real ranges of your proxy/CDN), or a private/reserved address, which is auto-trusted by default for compatibility with same-host reverse proxies. `X-Forwarded-Proto` is authoritative and fail-closed: any non-`https` segment rejects the request and `X-Forwarded-SSL: on` cannot override it. If a client could reach your origin directly from the private network, disable the automatic trust by returning `false` from the `vulnity_trust_private_proxies` filter (e.g. in a mu-plugin) and declare your proxies explicitly. Direct internet connections must satisfy `is_ssl()`.

= What is the HTTPS proxy shim and how do I disable it? =
When TLS terminates at a trusted proxy and the origin receives plain HTTP, WordPress' `is_ssl()` is false, and session cookies would be written (and later read) with the wrong scheme. On load, the plugin applies the standard WordPress reverse-proxy shim — setting `$_SERVER['HTTPS']` — but only after the same fail-closed validation as the SSO gate (trusted hop + all-`https` forwarded chain; never from an untrusted direct client). This keeps `is_ssl()` consistent site-wide so delegated-login cookies always carry the `Secure` attribute and remain readable in wp-admin. To disable it, set the option `vulnity_https_proxy_shim` to `0` or return `false` from the `vulnity_https_proxy_shim` filter; the canonical `X-Forwarded-Proto` shim in `wp-config.php` remains a valid alternative. Deliberate fail-closed: with the shim disabled and TLS only at the proxy, `/sso` answers 400 rather than ever issuing a non-`Secure` admin cookie.

One-time note: on a site behind a TLS-terminating proxy that did **not** already have an `is_ssl()` shim, activating or updating to this version flips `is_ssl()` to true, so WordPress starts using the secure auth cookie. Any admin sessions established before the update are invalidated once and those users are asked to log in again — a single expected re-login, not a recurring one. If you already run the `X-Forwarded-Proto` shim in `wp-config.php`, nothing changes.

= How do I disable delegated login on my site? (kill switch) =
Set the option `vulnity_delegated_login_enabled` to `0`, or return `false` from the `vulnity_delegated_login_enabled` filter from code you control (a mu-plugin or your theme). The filter is the last-resort guarantee: it works locally even if the central panel were compromised, so it will never be removed. While disabled, `/sso` answers 403 and no session can be created.

= How is the /sso rate limit enforced? =
Each redemption attempt atomically reserves one slot for the real client identity (resolved through the trusted proxy chain; IPv6 grouped by /64, IPv4 per host) in a fixed 300-second window, with a hard cap of 10 admitted attempts per identity and window — a concurrent burst cannot exceed it. A successful login refunds its slot, so only failures consume budget. The number of live rate-limit rows in `wp_options` is capped (oldest buckets are evicted deterministically, never merged into a shared bucket), and closed windows are swept hourly.

== Screenshots ==
1. Dashboard overview with real-time threat summary.
2. Alert detail screen showing remediation steps.
3. Settings page for configuring API credentials and scan schedules.

== Changelog ==
= 1.4.0 =
* Added assisted connection ("Connect automatically") from the Vulnity panel: a single-use, 15-minute linking token is exchanged for the site credentials, so connecting a site no longer requires copying a Site ID and Pairing Code by hand. The manual pairing form remains available as a fallback and is unchanged.
* The linking token never reaches the browser: the panel hands it to WordPress with a server-side POST, the plugin keeps it in a transient tied to the logged-in operator, and the exchange runs entirely server-side, so the bearer never appears in the URL, the fragment, the DOM, the page HTML or any browser JavaScript. Confirming the connection requires a valid nonce and re-authentication with your current WordPress password, and the pending token is discarded after three failed confirmations. The confirmation card names the organization and registered domain (fetched from the panel), and the exchange only succeeds when this site's URL matches the registered domain — a link opened on a different site connects nothing.
* Fixed the admin feedback for Test Connection, alert retries (single and bulk) and IP unblocking: the AJAX callbacks failed silently and left the screen with no result. They now show the outcome the server reports. Test Connection additionally reports a failed request instead of staying on "Testing...", and no longer stays blank when it is run a second time.
* The assisted exchange is idempotent: if the network response is lost after the token was already redeemed, the plugin can safely retry and recover the same credentials instead of failing with an "already used" error. The plugin also verifies that the pairing configuration actually persisted to the database (busting any object cache) before reporting success, and never follows HTTP redirects for pairing requests.
* The assisted flow now sends both `site_url` and `home_url`, so WordPress installed in a subdirectory (where they differ) pairs correctly instead of being rejected.
* Added per-site health telemetry surfaced in the Vulnity panel (storage mode, WP-Cron heartbeat health, and related warnings), plus self-healing of the WP-Cron heartbeat warning via a confirmatory runner.
* Hardened the pairing endpoint contract: an ambiguous or mode-less request body returns a stable 400 instead of a 500.

= 1.3.9 =
* Added delegated wp-admin login (SSO from the Vulnity panel) using one-time, single-use signed tokens. No WordPress password is stored or transmitted; the opaque token is consumed atomically and every access is audited in the SIEM.
* Added three REST endpoints (`/delegated-admins`, `/delegated-login`, `/sso`) protected by a reinforced v2 signature (HMAC over method+route+timestamp+nonce+body, with anti-replay). The existing endpoints are unchanged.
* The `/sso` endpoint returns a distinct status per token state: 404 unknown link, 409 already used (raising a high-severity replay alert in the SIEM), 410 expired. Consumed tokens leave a short-lived tombstone so reuse is always distinguishable from an invalid link, and every SSO response carries no-store cache headers.
* The `/sso` HTTPS requirement only trusts `X-Forwarded-Proto`/`X-Forwarded-SSL` when the immediate network hop is a trusted proxy (configured `vulnity_trusted_proxies` entries or private/reserved ranges); direct internet connections must satisfy `is_ssl()`, and the post-login redirect is always issued over HTTPS.
* The `/sso` rate limiter now keys on the real client IP resolved through the trusted proxy chain (IPv6 grouped per /64 so address rotation cannot evade the limit), counts only failed attempts, and increments atomically — bulk garbage requests behind a shared reverse proxy can no longer lock every visitor out of delegated login. Stale rate-limit buckets are swept by the hourly maintenance cron, not only on token registration, so a probed `/sso` cannot grow `wp_options` without bound.
* An explicit `X-Forwarded-Proto` is authoritative for the `/sso` HTTPS gate: a proxy-declared `http` (or a mixed chain) is rejected fail-closed and cannot be overridden by a residual `X-Forwarded-SSL: on`. For the strongest posture behind a TLS-terminating proxy, configure `vulnity_trusted_proxies` with the proxy IP/CIDR (or add the standard `X-Forwarded-Proto` HTTPS shim to `wp-config.php`); without it, private/reserved immediate hops are auto-trusted for compatibility with common localhost reverse-proxy setups.
* Delegated-login audit callbacks now require a 2xx response from the SIEM and are queued for retry (hourly, deduplicated server-side) when delivery fails. Each pending callback is stored as its own option row so a concurrent redemption during a retry pass can never drop an audit event.
* Deactivating the plugin revokes pending delegated-login tokens, and expired or revoked tokens keep answering 410 instead of 404 — including when a redemption races the revoke/sweep, which no longer raises a false replay alert.
* Admin session cookies created by delegated login always carry the `Secure` attribute (both the auth and the `logged_in` cookie). Behind a TLS-terminating trusted proxy the plugin applies a hardened, fail-closed HTTPS shim on load so `is_ssl()` is consistent when writing and reading the cookie (option/filter `vulnity_https_proxy_shim` to disable; with the shim off and TLS only at the proxy, `/sso` answers 400 rather than issuing a non-Secure cookie).
* The audit callback queue is loss-proof under pressure: deduplication happens before any eviction (a duplicate can no longer cost an unrelated pending event), the cap evicts deterministically oldest-first with every eviction logged, retries claim rows via compare-and-swap so two cron workers cannot double-deliver or lose attempt counts, redirects are never followed (3xx counts as failure), and events without a correlation id get a stable generated identity instead of being dropped.
* Revocation now covers every pending token (batched, not just the first 200), runs defensively on activation too, and a redemption that wins the delete race against an in-flight revocation re-checks the tombstone — a revoked token can never complete a login (410, no false replay alert).
* The `/sso` rate limiter enforces a hard admission cap per identity and window (atomic reserve-then-refund: a concurrent burst cannot exceed the limit and successful logins refund their slot), and the number of live rate-limit rows in `wp_options` is bounded with deterministic oldest-first eviction — identity floods can no longer grow the table without limit.
* New `vulnity_trust_private_proxies` filter to disable the automatic trust of private/reserved hops for forwarded headers and IP resolution (see the trust-model FAQ).

= 1.3.5 =
* Added WP-Cron diagnostics in the WordPress admin when scheduled Vulnity heartbeats appear disabled, missing, stale, or only recover through site activity.
* Added a throttled runtime heartbeat fallback from admin/site traffic for hosts where WP-Cron is disabled or unreliable.
* Added a WP-Cron setup documentation link to guide users toward a stable external cron configuration when needed.

= 1.3.4 =
* Hardened hidden login URL handling so direct unauthenticated access to wp-login.php and wp-admin returns 404 while the configured custom login slug remains usable.
* Reduced malicious upload false positives so normal media such as PNG files do not trigger PHP upload alerts.

= 1.3.2 =
* Added WP-CLI bulk pairing support for agency and VPS environments.
* Added safe dry-run validation before connecting a site from the command line.
* Added pairing-key support so new WordPress installs can connect to Vulnity without copying per-site pair codes manually.

= 1.3.1 =
* Version bump to 1.3.1.
* WordPress Multisite activation is blocked explicitly in 1.3.1 to avoid unsafe cross-blog configuration, storage, cron, or uninstall behavior.
* Minimum supported WordPress version is now 6.2; older versions are blocked fail-safe during activation/runtime.

= 1.2.3 =
* Fixed firewall bootstrap blocking wp-login.php, wp-cron.php, admin-ajax.php, and xmlrpc.php for blocked IPs — admins can now recover access.
* Fixed firewall bootstrap returning HTML instead of JSON for REST API requests from blocked IPs.
* Fixed uninstall leaving broken .htaccess when file is read-only — now creates safe stub to prevent HTTP 500.
* Fixed early IP blocking (plugins_loaded:0) intercepting AJAX and REST requests, breaking admin panel functionality.
* Reduced SIEM alert timeout from 10s to 3s to prevent page hangs during attacks.
* Reduced inventory sync timeout from 30s to 8s to prevent random slow page loads via pseudo-cron.
* Improved file detection in Protect Common Paths — now handles query strings, trailing slashes, and dotted directory names correctly.
* Added PHP execution blocking rule for uploads directory in generated Nginx configuration snippet.
* Expanded REST API public route whitelist: added WooCommerce v3, UpdraftPlus, BackWPup, Elementor, Forminator, FluentForms, SureCart, MailPoet, and block editor endpoints.
* Added `Options -Indexes` to Protect Common Paths .htaccess rules as defense-in-depth measure.
* Updated Stable tag from 1.2.2 to 1.2.3.

= 1.2.2 =
* Fixed anti-collapse dedup system blocking subsequent auto-update state toggle events due to identical hash.
* Fixed wrong authentication headers for `/real-time-alerts` endpoint (now uses HMAC-SHA256 signature instead of token).
* Fixed missing `remediation` field in auto-update state events sent to the SIEM.
* Fixed `version_old` not captured in auto-update events; now recorded via `upgrader_pre_install` hook before files are replaced.
* Fixed auto-update trigger running on disable; updates now only fire for newly enabled component types.
* Fixed auto-update event detection using `instanceof WP_Automatic_Updater` instead of `wp_doing_cron()` for broader compatibility.
* Fixed single-file plugin slug resolving to `.` (e.g. hello-dolly) in update event payloads.
* Added `triggered_by` field to update events: `siem_manual`, `siem_auto_update`, or `wp_auto_updater`.
* Auto-update toggles in the admin panel are now read-only; changes must be made from the SIEM.
* Replaced `parse_url()` with `wp_parse_url()` for WordPress coding standards compliance.

= 1.2.1 =
* Plugin Check compatibility improvements for filesystem and nonce-related warnings.
* Runtime validation improvements for scanner detection, file editor monitoring, and firewall state serialization.

= 1.2.0 =
* Fixed login URL rename validation against existing pages/posts and reserved WordPress routes.
* Fixed uninstall cron cleanup to use `wp_unschedule_hook()` for complete removal.
* Fixed heartbeat, mitigation sync, and alert buffer crons not cancelled on plugin disconnect.

= 1.1.9 =
* Send whitelist IPs (user public IP + localhost) to the SIEM during pairing so the whitelist persists after synchronization.

= 1.1.8 =
* Fixed Nginx warning notice appearing repeatedly on every admin page load; it now displays only once.
* Improved notice format: each protected path is shown on its own line for better readability.
* Added link to solution documentation for Nginx .htaccess compatibility.

= 1.1.7 =
* Fixed deactivation not clearing all cron jobs (4 missing hooks, plus events re-scheduled by late-firing alert hooks).
* Added `final_deactivation_cleanup` at priority 9999 to ensure complete cron and .htaccess cleanup after all hooks fire.
* Replaced `wp_clear_scheduled_hook` with `wp_unschedule_hook` to clear single events with arguments.
* Added native PHP fallback for .htaccess marker removal when WP_Filesystem is unavailable.
* Fixed Plugin Check error: replaced direct `is_writable()` with `vulnity_path_is_writable()` and `WP_Filesystem_Direct`.

= 1.1.5 =
* Fix uninstall multisite cleanup query when `sitemeta` table is not available to prevent SQL warnings in debug.log.

= 1.1.4 =
* Ensure uninstall removes Vulnity firewall/log folders recursively so no plugin-owned folders are left behind.

= 1.1.3 =
* Ensure uninstall removes Vulnity firewall/log folders even when permissions are restrictive by attempting safe chmod before cleanup.

= 1.1.2 =
* Added a dedicated Vulnity log with line-based rotation and safe fallbacks when uploads are not writable.
* Added admin warning when firewall storage cannot be written, with clear remediation guidance.
* Expanded uninstall cleanup to remove Vulnity log files and firewall artifacts across fallback paths.

= 1.1.1 =
* Fixed deactivation cleanup so Vulnity hardening marker blocks are removed fully from `.htaccess` without modifying user-defined rules.
* Improved deactivation safety in shared hosting environments with conservative, marker-only rollback behavior.

= 1.1.0 =
* Improved admin UI consistency across Dashboard, Synchronization, Mitigation, Hardening, and Setup screens.
* Hardened plugin lifecycle behavior for shared hosting compatibility and safer deactivation/uninstall flows.
* Added conservative server integration safeguards to reduce side effects in Apache/Nginx environments.

= 1.0.5 =
* Version bump to 1.0.5.

= 1.0.4 =
* Version bump to 1.0.4.

= 1.0.3 =
* Standardized admin asset enqueues and AJAX URL localization for compliant loading.
* Hardened nonce and capability checks across alerts and admin handlers.
* Improved path resolution using WordPress APIs for non-default installs.
* Documented external Supabase services used for alerts and mitigation updates.

= 1.0.2 =
* Initial release.

== Upgrade Notice ==
= 1.3.5 =
Adds WP-Cron diagnostics, documentation guidance, and a throttled traffic fallback for delayed heartbeats.

= 1.3.4 =
Hardens hidden login URL handling and reduces malicious upload false positives for normal media files.

= 1.3.2 =
Adds WP-CLI pairing support for connecting many WordPress sites to Vulnity from a VPS safely.

= 1.3.1 =
Version 1.3.1 release.

= 1.2.3 =
Critical stability fixes: prevents admin lockout from firewall, reduces SIEM request timeouts, fixes uninstall leaving broken .htaccess, and expands REST API compatibility with popular plugins.

= 1.2.2 =
Fixes bidirectional auto-update sync with the SIEM: corrects authentication headers, dedup hashing, version tracking, and update trigger logic.

= 1.2.1 =
Maintenance release with Plugin Check compatibility fixes.

= 1.2.0 =
Fixes login URL validation and cron cleanup on disconnect.

= 1.1.9 =
Whitelist IPs are now sent to the SIEM during pairing to prevent them from being lost on sync.

= 1.1.8 =
Nginx warning now shows only once and includes a link to the solution documentation.

= 1.1.7 =
Deactivation now fully clears all cron jobs and .htaccess markers, including events re-scheduled by alert hooks.

= 1.1.5 =
Fixes a multisite uninstall query edge case that could log an SQL warning.

= 1.1.4 =
Uninstall cleanup now removes Vulnity firewall/log folders recursively so nothing is left behind.

= 1.1.3 =
Improved uninstall cleanup for firewall/log folders in restrictive hosting environments.

= 1.1.2 =
New rotating Vulnity logs plus safer firewall storage warnings and cleanup behavior for shared hosting.

= 1.1.1 =
Conservative `.htaccess` cleanup update: Vulnity now removes only its own marker blocks on deactivation and leaves user rules untouched.

= 1.1.0 =
Stability and compatibility update focused on safer lifecycle handling and cleaner admin UX.

= 1.0.5 =
Version bump to 1.0.5.

= 1.0.4 =
Version bump to 1.0.4.

= 1.0.3 =
Compliance-focused update to align asset loading, documentation, and escaping with WordPress.org guidelines.

= 1.0.2 =
Initial public release featuring Vulnity SIEM integration and security monitoring dashboard.

== License ==
This plugin is licensed under the GNU General Public License v2.0 or later. You are free to redistribute and/or modify it under the terms of the GPL as published by the Free Software Foundation. The complete license text is included in the bundled `license.txt` file and is also available online at https://www.gnu.org/licenses/gpl-2.0.html.
