=== SMEPlan Security Shield ===
Contributors: solotop
Tags: security, malware scan, firewall, hardening, backdoor
Requires at least: 6.1
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 0.7.32
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Scheduled security scanning (files/DB/config), baseline/integrity checks, safe quarantine & rollback, and hardening for WordPress.

== Description ==

SMEPlan Security Shield is a free, open-source security plugin built around a practical WordPress operations checklist: it watches the 3 most common attack surfaces (OWASP-class attacks plus WordPress-specific ones, persistence mechanisms, and entry vectors), detects issues with baseline/checksum + signature + thresholded heuristics, and remediates safely (quarantine instead of outright deletion; 1-click rollback).

= Key features =

* **Batched, checkpointed file scanning**: prioritizes `mu-plugins`, drop-ins, the active theme/plugins, and `uploads`; never loads the whole file tree into RAM at once.
* **Safe database scanning**: keyset pagination (no `OFFSET`) over `options`/`posts`/`postmeta`; only flags a row when it decodes into an actually executable PHP/JS token, skipping image data URIs.
* **Configuration checks**: `.htaccess`/`.user.ini` rules that map media extensions to PHP, `auto_prepend_file`, file/directory permissions, weak salts/keys, unusual cron entries.
* **Baseline/integrity**: compares core files against WordPress.org's official checksums; automatically builds a SHA-256 baseline for every plugin/theme on install/update; 1-click restore of any mismatched core file from a signature-verified WordPress.org package, with the current file quarantined first.
* **Quarantine & rollback**: moves suspicious files aside (never deletes), with a full transaction log and 1-click rollback.
* **Per-component backups**: a "last confirmed good" snapshot of each plugin/theme, refreshed on every trusted update, checksum-verified before every restore, with a best-effort local tamper-resistance layer.
* **Maintenance mode with a TTL** that turns on automatically when remediation touches a hot path or a large batch, and turns itself off once a health-check passes.
* **Hardening**: login rate-limit/lockout by IP + IP/username (real IP behind a CDN via trusted proxies), disables XML-RPC pingback + caps `system.multicall`, security headers (HSTS/X-Frame-Options/CSP Report-Only), controlled auto-updates (low-traffic time window, skips VCS-managed sites, health-check after updating).
* **Multi-layer scan scheduling**: WP-Cron + an internal watchdog + an HMAC-signed REST endpoint (for system cron/remote pings) + a WP-CLI command — the schedule keeps running even when WP-Cron is unreliable.
* **Multisite**: enumerates every site by `blog_id`, scanning each site's own `uploads` folder and tables.

= Not yet in this release (planned for later versions) =

* 2FA (TOTP) and CAPTCHA for the login page.
* Anonymous telemetry (opt-in).
* Translations (every string is already wrapped in `__()`, ready for translators via translate.wordpress.org — no translation is bundled with the plugin itself).
* Action Scheduler integration for enterprise-grade durable queuing.

== Installation ==

1. Upload the plugin to `wp-content/plugins/` or install it through the Plugins screen.
2. Activate the plugin.
3. Go to **Security Shield → Wizard** to check WP-Cron/loopback and configure trusted proxies if the site sits behind a CDN.
4. See **Security Shield → Policies** to turn hardening features on/off as needed.

== Frequently Asked Questions ==

= Does the plugin delete suspicious files automatically? =

No. Files at the "malicious" level are **moved into quarantine** (`wp-content/uploads/smeplan-security-shield/quarantine/`) rather than deleted, and can be rolled back with 1 click. Files at the "suspicious" level are only recorded, waiting for manual review on the Findings page.

Quarantined files are kept for a limited time: once a session is older than the retention period set under Policies (14 days by default), it is removed automatically to stop the quarantine folder growing without bound. Roll back anything you want to keep before that window closes, or raise the retention setting.

= What is removed when I delete the plugin? =

Deleting the plugin removes every option it created, its scheduled events, and the three custom capabilities it grants. It **deliberately does not remove its storage folder** at `wp-content/uploads/smeplan-security-shield/`, which holds the quarantine, the file baseline, the event log and any component backups.

This is a deliberate choice, not an oversight. Quarantined files are *moved*, not copied — the folder holds the only remaining copy of anything the plugin took out of the site. Deleting a plugin is easy to do in the middle of handling an incident, or by someone who is not the person investigating, and having that click silently destroy both the only rollback path and the only forensic evidence would be the wrong default for a security plugin. Reinstalling brings the previous quarantine and logs straight back.

To remove it, delete that folder yourself over SFTP or your host's file manager once you are sure nothing in it is still needed. **Read the caution first:** the `quarantine/` subfolder can contain live malicious files that were pulled off the site. Delete them, do not move them back into place.

= Does the plugin automatically edit database content or the .htaccess file? =

No. Every finding in the database or in configuration files (.htaccess/.user.ini) is only reported, never auto-fixed, to avoid breaking a site's legitimate functionality.

= Does this plugin change how WordPress updates itself? =

No. It never supplies its own update source: there is no bundled update checker, no third-party update server, no filtering of the plugin-information API, and nothing written to the transients core caches available updates in. Everything WordPress installs still comes from WordPress.org, fetched and verified by WordPress itself.

What it does offer — off by default, and only if you switch it on in **Policies** — is control over *when* an update WordPress has already found and verified gets applied. Using core's own public `auto_update_core` / `auto_update_plugin` / `auto_update_theme` filters, it can hold an update back until the low-traffic window you configure, skip it while the site is under load, and skip any plugin or theme directory that is under version control (updating those breaks a deploy). It only ever delays WordPress's own updates; it never substitutes them.

Since 0.7.32 it can also refuse one: a plugin or theme package is scanned while still in its temporary folder, and if a file in it matches malware detection the install is stopped before anything is written. A manual update can be allowed through once from the dashboard notice; an automatic one is refused and reported. This uses core's own `upgrader_source_selection` filter, changes nothing about where updates come from, and can be switched off in Policies.

Automated scanners flag any use of those filters for a human to look at, which is why this is spelled out here.

= What environment does it need? =

WordPress 6.1+, PHP 7.4+. Works best when WP-Cron runs normally; if the site has `DISABLE_WP_CRON` set or blocks loopback requests, use system cron/WP-CLI following the instructions on the Wizard page.

== Screenshots ==

1. SMEPlan Security Shield Dashboard and Security Scanner Overview.

== Privacy Policy ==

By default, this plugin does not send any data outside of the site it is installed on. Everything it collects (scan findings, logs, baseline data) stays in the local WordPress database and in a protected local storage folder inside the uploads directory (`wp-content/uploads/smeplan-security-shield/`, blocked from direct web access).

Two features send data off-site, and both are entirely opt-in — off unless the site admin explicitly sets them up:

* **Alert email**: if enabled, a summary of new findings is emailed to the site's configured admin email address (`admin_email`) using WordPress's own `wp_mail()`.
* **Alert webhook**: if the admin enters a Webhook URL in Policies, a summary (site URL, alert subject, malicious/suspicious counts, timestamp — no personal or visitor data) is sent as JSON to that admin-provided URL whenever new findings are detected. Nothing is sent anywhere unless the admin fills in this field themselves.

That storage folder outlives the plugin on purpose: deleting the plugin removes its options, cron events and capabilities, but leaves the folder in place so a quarantined file is never destroyed by an uninstall performed mid-incident. See the FAQ entry "What is removed when I delete the plugin?" for the reasoning and for how to remove it yourself.

The plugin does not phone home to any SMEPlan-operated server, does not track usage/analytics, and does not include any third-party tracking or advertising code.

== Changelog ==

Only the most recent releases are listed here, because WordPress.org
truncates this section at 5,000 characters. The complete history — every
release, in full — is in `changelog.txt`, included with the plugin.

= 0.7.32 =
Thirteen fixes from a third full review of 0.7.31, plus a new Trusted components screen. Each fix is covered by a test that fails against 0.7.31.
* **Data loss/availability:** a core-file restore could leave the file missing from disk if the process died mid-way (fatal on every request); rolling back a single file silently overwrote whatever was at the destination with no copy kept; restoring a component from backup ran with the site live, so a request in that window could get the plugin permanently deactivated by core; uninstalling could strand a `.maintenance` file at the site root that nothing would ever remove. (ss-4, rc-E, ss-7, ss-2)
* **Scanning:** one multi-GB file could stall the scanner forever with no progress; malware quarantined while preparing a backup never raised an email/webhook alert; network-activated plugins were never treated as hot paths on multisite. (rc-G, rc-D, M6)
* **Migration:** sites stamped between 0.7.26 and 0.7.30 could hold quarantine sessions that were permanently un-rollbackable, and a failing adoption re-ran on every page view. Backups also became unreachable after a delete-then-reinstall. (rc-U, M16, ss-1)
* **Docs:** readme said quarantined files are never deleted; the retention window (14 days by default) is now documented. (rc-T)
* **Accepting a false positive:** a suspicious file finding used to offer only "Quarantine" — nothing to say "I reviewed this, it is fine". A new **Accept this file** action records the file's hash and stops reporting it; edit the file and it is reported again immediately. Accepted files are reviewable and revocable on the Trusted components screen.
* **Finding out why something was quarantined:** the quarantine table on Remediation had no heading, so the page looked like it began at Component backups and the one table that explains each entry was easy to miss. It now has a heading and a note on reading the Reason column — an automatic quarantine names the detections that fired, while "replaced during backup restore" means it is simply the previous version kept after an action you took. Dashboard counters are now clickable.
* **A restore's undo copy no longer looks like a threat:** restoring a component quarantines the current version so the action can be undone, but that entry used to sit in the same list as files the scanner found, inflating the Dashboard count. Detections and undo copies are now separate lists, and the counter (now "Quarantined by a scan") counts only real findings.
* **Restoring a false positive no longer takes the site down:** rolling a quarantined file back used to invite the next scan to quarantine it again, and in an active plugin that also switched on maintenance mode — locking the admin out of the very page needed to fix it. A new **Restore and accept (false positive)** action accepts the file's recorded checksum before putting it back, breaking the loop.
* **New — incoming plugin/theme packages are scanned before installation.** A package that trips malware detection is refused while it is still in a temporary folder, so the installed version is never touched. A manual update shows what was found and offers a single-use "allow this once"; an automatic update is refused outright but raises a critical alert and a dashboard notice, because a silently blocked auto-update would leave the site unpatched while looking up to date. Files you have already accepted are skipped, so a vendored library cannot block a plugin's releases forever.
* **Automatic quarantine now depends on where the file came from.** Pulling a file out of an active, established plugin or theme is what turns one false positive into a broken site, so that case is now reported at critical level and left for you to action with the Quarantine button (which is offered for any finding whose file is still on disk). Everywhere removal is safe — uploads, mu-plugins, drop-ins, loose files, inactive components, and anything installed moments ago — quarantine stays automatic and immediate. A Policies setting restores the old behaviour.
* **Automatic maintenance mode is now OFF by default,** because it also blocked wp-admin — so nobody could reach this plugin to undo it, and deleting the plugin does not help (`.maintenance` sits in the site root). When switched on, the maintenance page now explains itself to anyone already signed in, including how to end it immediately.
* **New — Trusted components screen:** see and manage which plugins/themes you have vouched for. Trusting records a hash of every file as the known-good state (nothing is excluded from scanning); any later change is still reported, and trust can be withdrawn at any time.

Older releases: see `changelog.txt`, included with the plugin.

== Upgrade Notice ==

= 0.7.32 =
Fixes four ways this plugin could itself lose data or take a site down (a core-file restore leaving the file missing, a rollback overwriting the destination, a backup restore running with the site live, and a stranded .maintenance file after uninstall), stops one large file stalling the scanner, and restores quarantine recovery on sites upgraded from 0.7.26-0.7.30. Adds a Trusted components screen. Recommended for all sites.

= 0.7.31 =
Follow-up fixes to 0.7.30: restores legacy-quarantine recovery after a reinstall, stops the scanner from restarting without ever finishing, and hardens the 0.7.30 alert and cron-example fixes. Recommended for all sites.

= 0.7.30 =
Fourteen security and correctness fixes from a full review of 0.7.29, several affecting multisite recovery, quarantine rollback, and automatic backups. Recommended for all sites.

= 0.7.29 =
Stops WordPress 6.7+ logging a "translation loaded too early" notice on every admin page load. Recommended if your debug log is filling up.

= 0.7.28 =
Removes a debug warning added in 0.7.25 that flooded the log on sites with WP_DEBUG enabled. No behaviour change otherwise.

= 0.7.27 =
Stops WordPress 6.7+ filling the debug log with a "translation loaded too early" notice on every request. No behaviour change otherwise; 0.7.26's security fix is included.

= 0.7.26 =
Security release. Fixes an arbitrary file write inside the WordPress root: a forged quarantine session could make Rollback overwrite wp-config.php. Affects 0.7.24 and 0.7.25. Update, then review the Remediation screen before clicking Rollback if the site may have been compromised.
