# Changelog - Vulnity Security

## [1.4.0] - 2026-07-18

### Added
- Assisted connection ("Connect automatically") from the Vulnity panel: a single-use, 15-minute linking token is exchanged for the site credentials, so pairing no longer requires copying a Site ID and Pairing Code by hand. The manual pairing form remains available, unchanged, as the fallback.
- Confirmation card that names the requesting organization and the registered domain. The plugin resolves that identity against the panel server-side, without consuming the token, so the operator sees who is connecting what before confirming.
- Per-site health telemetry surfaced in the panel (storage mode, WP-Cron heartbeat health and related warnings) with self-healing of the WP-Cron heartbeat warning via a confirmatory runner.
- Reproducible release packaging: `.gitattributes` `export-ignore` plus `bin/build-release.sh`, which builds the ZIP from `git archive` and fails if it would contain `tests/`, `bin/`, `.git*` or dev artifacts.

### Security
- The linking token never reaches the browser. The Vulnity panel hands it off to WordPress with a server-side POST to `admin.php?page=vulnity`, the plugin keeps it in a transient tied to the logged-in operator, and the exchange for the site credentials runs exclusively server-side. The bearer therefore does not appear in the URL, the fragment, the DOM, the rendered HTML or any browser JavaScript, so no script running on the page can read it.
- Confirming an assisted connection is a server-side ceremony: a valid WordPress nonce **and** re-authentication with the operator's current WordPress password. A hostile script on the page can forge a click or submit the form programmatically, but it cannot supply the password, so it cannot authorise the exchange; three failed confirmations discard the pending token.
- The pending token is consumed on a successful exchange and deleted when the flow is cancelled, when it expires (15 minutes) or when it is discarded after repeated failed confirmations.
- The assisted exchange only succeeds when this site's URL (home or site URL) matches the domain registered in the panel — both for the identity preview (anti-oracle / anti-phishing: a link opened on a different site reveals nothing and connects nothing) and for the redemption itself.
- The plugin verifies that the pairing configuration actually persisted to the database — busting any persistent object cache before re-reading — and strictly validates the returned credentials (rejecting non-string/malformed values) before declaring success, so a lost write or malformed response can never masquerade as a completed pairing.
- Pairing requests never follow HTTP redirects (`redirection=0`), so the bearer is never re-sent to an unexpected destination by a 3xx.

### Fixed
- Admin actions that report back from an AJAX call — Test Connection, retrying a single alert, retrying all failed alerts and unblocking an IP — showed no result at all: the callbacks referenced helpers that only existed inside the `jQuery(document).ready()` closure, so the response arrived and the handler died before painting anything. Test Connection additionally had no failure handler, so a request that never completed left "Testing..." on screen forever, and its result faded out permanently — the second time it was run it showed nothing at all until the page was reloaded.
- The assisted exchange is idempotent within a short window: if the network response is lost after the token was already redeemed, the plugin retries once and recovers the same credentials instead of failing with an "already used" error.
- 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 with `missing_home_url`.
- The pairing endpoint returns a stable `400 invalid_request` for an ambiguous or mode-less request body instead of a `500`.

## [1.3.9] - 2026-07-13

### Added
- Delegated wp-admin login (SSO from the Vulnity panel) with one-time signed tokens, atomic consumption, per-state responses (404/409/410), tombstones, replay alerts and full SIEM auditing over three new v2-signed REST endpoints (`/delegated-admins`, `/delegated-login`, `/sso`).
- Hardened HTTPS proxy shim applied on plugin load: when TLS terminates at a trusted proxy (fail-closed validation identical to the `/sso` gate), `$_SERVER['HTTPS']` is set so `is_ssl()` is consistent site-wide. Disable via the `vulnity_https_proxy_shim` option or filter.
- `vulnity_trust_private_proxies` filter to turn off the automatic trust of private/reserved network hops (trust model documented in readme.txt).

### Security (second release-gate hardening)
- Admin session cookies from delegated login always carry `Secure` — auth and `logged_in` — with an explicit contract at the call site plus the shim for read-side consistency; with the shim disabled and TLS only at the proxy, `/sso` fails closed with 400 instead of ever issuing a non-Secure cookie.
- Audit callback queue: dedup-before-evict (a duplicate can no longer drop an unrelated pending event), deterministic logged oldest-first eviction, compare-and-swap claims in the retry cron (no lost updates or double delivery), `redirection=0` (3xx is a failure), and stable generated correlation ids so no audit event is ever silently discarded.
- Revocation covers all pending tokens in batches (not just the first 200), also runs defensively on activation, and redemptions that win the delete race against an in-flight revocation re-check the tombstone — a revoked token can never complete a login.
- `/sso` admission is a hard cap under concurrency (atomic reserve-then-refund; successful logins refund their slot, only failures consume budget) and live rate-limit rows in `wp_options` are bounded with deterministic oldest-first eviction, closing the identity-flood growth vector.

## [1.3.5] - 2026-06-04

### Added
- Added WP-Cron diagnostics in the WordPress admin for missing, stale, disabled, or fallback-only heartbeat scheduling.
- Added a throttled runtime heartbeat fallback from admin/site traffic for hosts where WP-Cron is disabled or unreliable.
- Added WP-Cron setup documentation guidance for stable external cron configuration.

## [1.3.4] - 2026-06-01

### Fixed
- Hardened the hidden login URL feature so direct unauthenticated access to wp-login.php and wp-admin returns 404 while the configured custom login slug remains usable on Apache and nginx/PHP-FPM setups.
- Fixed custom login URL generation so WordPress login links point to the Vulnity login entrypoint instead of returning an empty login URL.
- Reduced malicious upload false positives so the PHP upload alert only fires for PHP-like uploads with suspicious PHP indicators, not normal media such as PNG files.

## [1.3.0] - 2026-05-10

### Changed
- Bumped plugin version from 1.2.3 to 1.3.0.
- Updated `readme.txt` Stable tag from 1.2.3 to 1.3.0.
- WordPress Multisite activation is blocked explicitly in 1.3.0 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] - 2026-03-31

### Stability & Compatibility — Critical Fixes from Production Audit

#### Fixed
- **Firewall bootstrap blocks admin recovery paths:** Blocked IPs could not access `wp-login.php`, `wp-cron.php`, `admin-ajax.php`, or `xmlrpc.php`, making it impossible for admins to recover access. Added path-based exemptions before the block page renders.
- **Firewall bootstrap returns HTML for REST API:** Blocked IPs hitting `/wp-json/` received an HTML 403 page instead of JSON, crashing mobile apps and API integrations. Now returns `{"code":"ip_blocked","message":"Access denied by Vulnity Firewall","data":{"status":403}}`.
- **Uninstall leaves broken `.htaccess` on read-only filesystems:** If `.htaccess` was not writable during uninstall, the `auto_prepend_file` directive pointed to a deleted `bootstrap.php`, causing HTTP 500 on all requests. Now detects failed cleanup and creates a safe no-op stub.
- **Early IP blocking intercepts AJAX and REST requests:** `early_blocking_check()` at `plugins_loaded` priority 0 had no exemptions for `DOING_AJAX` or `REST_REQUEST`, returning HTML block pages instead of JSON and breaking the admin panel for blocked IPs.
- **`request_targets_file()` false positives:** The file detection method used a simple dot-presence check, incorrectly treating directories with dots in their names as files. Now strips query strings, checks trailing slashes, and validates against a proper file extension regex.

#### Changed
- **SIEM alert timeout reduced from 10s to 3s:** Prevents page hangs when the SIEM endpoint is slow or unreachable during attacks. Retry mechanism handles any resulting timeouts.
- **Inventory sync timeout reduced from 30s to 8s:** Prevents random 30-second page loads for visitors who trigger WordPress pseudo-cron.
- **SIEM alert redirections reduced from 5 to 2:** Limits redirect chains to reduce total blocking time.
- **Nginx config snippet now blocks PHP execution in uploads:** Added `location ~* ^/wp-content/uploads/.*\.php$ { return 403; }` to prevent uploaded webshell execution.
- **`.htaccess` common paths rules now include `Options -Indexes`:** Added as defense-in-depth alongside the existing `RewriteRule`.

#### Added
- **Expanded REST API public route whitelist:** Added WooCommerce REST v3, UpdraftPlus, BackWPup, Elementor, Forminator, FluentForms, SureCart, MailPoet, and WordPress block editor endpoints. All prefixes made more specific (e.g. `/contact-form-7/v1/` instead of `/contact-form-7/`).

#### Coding Standards
- Updated `readme.txt` Stable tag from 1.2.2 to 1.2.3.

---

## [1.2.2] - 2026-03-19

### Auto-Update System — Bug Fixes & SIEM Bidirectionality

#### Fixed
- **Anti-collapse dedup blocking second toggle:** `generate_hash()` produced the same hash for all `auto_update_state_changed` events because those events carry no `ip`, `user_id` or `action` fields. Hash now includes plugin/theme toggle state (`:p=0/1:t=0/1`), making each combination distinct.
- **`/real-time-alerts` auth failure:** Endpoint requires HMAC-SHA256 (`x-signature` + `x-site-id` + `x-plugin-version`) but plugin was sending `x-vulnity-token`. `send_alert()` now selects headers based on target endpoint.
- **Missing `remediation` field:** SIEM canonical schema requires `remediation.summary` and `remediation.steps`. Added to `send_auto_update_state_event()`.
- **`version_old` missing from auto-update events:** By `upgrader_process_complete` files are already replaced, making the old version unrecoverable. Fixed by hooking `upgrader_pre_install` to capture the version before WP overwrites plugin/theme files.
- **Updates re-triggered when disabling auto-update:** `ajax_save_auto_update()` was running `apply_push_trigger_update()` whenever at least one type was ON, including when disabling. Now reads previous state first and only triggers for types that are *newly* being enabled.
- **`wp_doing_cron()` too restrictive:** Check prevented auto-update alerts when `wp_maybe_auto_update()` was invoked outside a standard cron context (e.g. via `wp eval`). Replaced with `$upgrader instanceof WP_Automatic_Updater`, which correctly identifies WP's native updater in all contexts.
- **Single-file plugin slug resolving to `.`:** `dirname('hello-dolly.php')` returns `'.'`, breaking slug-based lookups. Fixed with `basename($file, '.php')` fallback in both `apply_push_trigger_update()` and `on_auto_update_complete()`.

#### Added
- **`triggered_by` field:** Distinguishes `siem_manual` (SIEM explicit request), `siem_auto_update` (triggered by enabling auto-update), and `wp_auto_updater` (WordPress native cron updater) in manual-update and auto-update events.

#### Changed
- **Auto-update toggles are now read-only in the admin panel.** An info banner redirects administrators to the SIEM (Sistema section) to enable or disable auto-updates, preventing state divergence between WP admin and the SIEM.

#### Coding Standards
- Replaced `parse_url()` with `wp_parse_url()` across `class-core.php` and `class-auto-update-alert.php`.
- Added `phpcs:disable/enable` comment for nonce verification in `ajax_save_auto_update()` (nonce already verified via `verify_admin_ajax_request()`).
- Updated `readme.txt` Stable tag from 1.2.1 to 1.2.2.

---

## [1.2.1] - 2026-03-09

### Maintenance Release
- **Changed:** Version bump to 1.2.1.
- **Improved:** Plugin Check compatibility for filesystem and nonce-related warnings.
- **Improved:** Runtime validation of scanner detection, file editor monitoring, and firewall state serialization.

## [1.2.0] - 2026-03-06

### Improvements & Fixes
- **Fixed:** Login URL rename now validates the chosen slug against existing WordPress pages/posts before saving, preventing rewrite rule conflicts.
- **Fixed:** Login URL rename now blocks reserved WordPress routes (`wp-admin`, `wp-json`, `wp-login`, `admin`, `login`, etc.) as login slug candidates.
- **Fixed:** `vulnity_suppress_blocked_alerts` option added to the explicit uninstall cleanup list.
- **Fixed:** Uninstall now uses `wp_unschedule_hook()` instead of `wp_clear_scheduled_hook()` for consistent and complete cron cleanup, matching the deactivation behavior.
- **Fixed:** Heartbeat, mitigation sync, retry queue and alert buffer crons are now cancelled on disconnect (both manual unpair and remote disconnect), preventing unnecessary scheduled tasks while the plugin is unpaired.

## [1.1.8] - 2026-02-12

### Fix Wrning error
- **Fixed:** Warning message was displayed on every load; now it is shown only once.

## [1.1.7] - 2026-02-12

### Deactivation & Cleanup Fixes
- **Fixed:** 4 missing cron hooks not cleared on deactivation (`vulnity_sync_mitigation_config`, `vulnity_cleanup_flood_data`, `vulnity_process_brute_force_windows`, `vulnity_triggered_inventory_sync`)
- **Fixed:** Cron events re-scheduled by late-firing alert hooks (e.g. plugin-change alerts triggering inventory sync) now cleared via `final_deactivation_cleanup` at priority 9999
- **Fixed:** Replaced `wp_clear_scheduled_hook` with `wp_unschedule_hook` to properly clear single events with arguments
- **Fixed:** `.htaccess` marker removal now has native PHP fallback when WP_Filesystem is unavailable during deactivation
- **Fixed:** Plugin Check error — replaced direct `is_writable()` call with `vulnity_path_is_writable()` and `WP_Filesystem_Direct`

## [1.1.6] - 2026-02-11

### 🔴 Critical Bug Fixes

#### Race Conditions & Concurrency
- **Fixed:** Race condition in log rotation that could cause data loss when multiple processes rotate logs simultaneously
  - Added atomic file locking with `flock(LOCK_EX | LOCK_NB)`
  - Implemented double-check pattern after lock acquisition
  - Files: `vulnity.php:471-518`

- **Fixed:** Race condition in `save_local_alert()` that could lose critical alerts
  - Implemented transient-based locking with 10s timeout
  - Added try-finally block to ensure lock release
  - Files: `includes/alerts/class-alert-base.php:138-177`

#### Memory Exhaustion
- **Fixed:** Memory exhaustion when counting lines in large log files (100MB+)
  - Replaced full file loading with streaming using `fgets()`
  - Now processes files line-by-line without loading into RAM
  - Files: `vulnity.php:425-448`

#### Stack Overflow & Performance
- **Fixed:** Recursive retry logic with blocking `sleep()` causing stack overflow and timeouts (up to 75s)
  - Converted recursive function to iterative loop
  - Removed blocking `sleep()` calls
  - Delegated retries to asynchronous cron jobs
  - Files: `includes/alerts/class-alert-base.php:153-186`

- **Fixed:** Fatal error when calling `wp_get_theme()->get('Name')` on missing theme
  - Created safe helper method `get_safe_theme_name()` with validation
  - Returns 'Unknown' instead of crashing
  - Files: `includes/class-security-monitor.php:510-521`

### 🟠 Security Improvements

#### Input Sanitization (Plugin Check Compliance)
- **Added:** Global helper function `vulnity_get_server_var()` using `filter_input(INPUT_SERVER)`
  - Complies with WordPress Plugin Check standards
  - Includes fallback for CLI/test environments
  - Files: `vulnity.php:533-579`

- **Improved:** Replaced 15+ direct `$_SERVER` accesses with sanitized helper
  - `includes/alerts/class-alert-base.php` - 6 locations
  - `includes/class-static-security.php` - 5 locations
  - `includes/alerts/class-file-editor-alert.php` - 2 locations
  - `includes/alerts/class-suspicious-query-alert.php` - 2 locations

#### Code Quality
- **Fixed:** Weak comparison (`==`) changed to strict comparison (`===`)
  - Files: `includes/class-anti-collapse.php:114`

### 🔵 Apache/Nginx Compatibility

#### Server Detection & Configuration
- **Added:** Server type detection (`detect_server_type()`)
  - Detects Apache, Nginx, LiteSpeed, or Unknown
  - Files: `includes/class-static-security.php`

- **Added:** Nginx configuration snippet generator (`get_nginx_config_snippet()`)
  - Dynamically generates Nginx config based on active features
  - Includes login URL rewriting, path protection, firewall bootstrap, and log protection
  - Files: `includes/class-static-security.php`

- **Added:** New admin view for server configuration
  - Auto-detects server type and shows appropriate instructions
  - Copy-to-clipboard functionality for Nginx config
  - Step-by-step installation guide
  - Files: `views/admin-server-config.php` (NEW)

#### PHP-Only Protection Fallback
- **Improved:** Enhanced `protect_common_paths()` with universal PHP fallback
  - Always protects Vulnity logs directory (`/wp-content/uploads/vulnity-logs/`)
  - Always protects firewall data directory (`/wp-content/vulnity-firewall/`)
  - Works on all servers (Apache, Nginx, LiteSpeed, etc.)
  - Files: `includes/class-static-security.php:777-821`

### 🟡 Internationalization (i18n)

- **Added:** Translation support for admin dashboard
  - 20+ strings wrapped in `__()`, `esc_html__()` functions
  - Files: `views/admin-dashboard.php`

- **Added:** Complete i18n support for server configuration view
  - 100% translatable with proper text domain
  - Files: `views/admin-server-config.php`

### ⚡ Performance Optimizations

- **Added:** Performance limit in alert processing
  - Limited to last 200 alerts to prevent timeouts
  - Prevents memory exhaustion with large alert history
  - Files: `includes/class-mitigation-manager.php:735-738`

---

## Technical Details

### Files Modified (10)
1. `vulnity.php` - Core functions and helpers
2. `includes/alerts/class-alert-base.php` - Alert system improvements
3. `includes/class-security-monitor.php` - Safe theme detection
4. `includes/class-anti-collapse.php` - Strict comparisons
5. `includes/class-static-security.php` - Nginx support + hardening
6. `includes/class-mitigation-manager.php` - Performance limits
7. `includes/alerts/class-file-editor-alert.php` - Input sanitization
8. `includes/alerts/class-suspicious-query-alert.php` - Input sanitization
9. `views/admin-dashboard.php` - Internationalization
10. `views/admin-server-config.php` - **NEW FILE**

### New Functions & Methods
- `vulnity_get_server_var()` - Global helper for sanitized `$_SERVER` access
- `Vulnity_Static_Security::detect_server_type()` - Server detection
- `Vulnity_Static_Security::get_nginx_config_snippet()` - Nginx config generator
- `Vulnity_Static_Security::get_apache_config_info()` - Apache info
- `Vulnity_Security_Monitor::get_safe_theme_name()` - Safe theme name retrieval

### Statistics
- **Lines Modified:** ~500
- **Lines Added:** ~350
- **Bugs Fixed:** 8 critical + 7 high priority
- **Security Issues:** 15+ locations sanitized
- **New Features:** 4 (server detection, config generator, PHP fallback, new admin view)

---

## Upgrade Notes

### Breaking Changes
None. This is a backward-compatible release.

### Action Required for Nginx Users
If you're running Nginx, visit **Settings > Vulnity > Server Configuration** to get your custom configuration snippet and installation instructions.

### Recommendations
1. If using Nginx, configure server-level rules for optimal security
2. Review new server configuration page at **Settings > Vulnity > Server Configuration**
3. Test alert generation to verify lock improvements work correctly

---

## Previous Releases

### [1.1.5] - Previous version
- Initial SIEM integration features
- Basic alert system
- Login URL renaming
- Common paths protection

---

**Full Changelog:** https://github.com/vulnity/vulnity-wp-plugin/compare/1.1.5...1.1.6
