=== Mintwall Security – Firewall, Malware Scanner, 2FA & Ad-Injection Scan ===
Contributors:      oxyazilim
Tags:              security, firewall, malware, 2fa, ad injection
Requires at least: 6.5
Tested up to:      7.1
Requires PHP:      7.4
Stable tag:        2.4.0
License:           GPLv2 or later
License URI:       https://www.gnu.org/licenses/gpl-2.0.html

Pre-WordPress firewall, 2FA login, malware & ad-injection scanner, file integrity and a hardening audit. Every change is reversible.

== Description ==

Mintwall Security protects WordPress at the earliest point your host allows, and
tells you honestly which layers it could and could not enable on your site. It
never claims a protection it cannot deliver in your environment.

**What runs for free, forever**

* A firewall that loads before WordPress (auto_prepend -> mu-plugin -> plugin), enforces a WordPress-specific signature rule set, and reports the exact level your host allowed
* Two-factor authentication: TOTP authenticator apps, passkeys (WebAuthn) and one-time recovery codes, with the setup QR generated on your own server. A passkey counts as the second factor at the login gate, so an account whose only second factor is a passkey is not waved through
* Brute-force login protection through a single, unavoidable identity gate
* Bot challenge (proof-of-work) on the login form
* User-enumeration defense: author scans, REST user listing and uniform login errors
* Session management with bulk revocation
* Scheduled malware scanning of wp-content by PHP token/AST analysis, not plain-text matching (eval/base64 chains, webshell patterns, entropy)
* Scheduled ad-injection scanning of options, posts and meta for injected scripts, iframes, redirects and obfuscated JavaScript
* Upload hardening and PHP execution blocking in wp-content/uploads
* REST and AJAX rate limiting
* Security headers with a Content-Security-Policy report-only rollout
* Critical-file and drop-in monitoring (wp-config.php, .htaccess, object-cache.php and more): a daily baseline, and drift that keeps being reported until you accept it
* Quarantine with human-approved restore — nothing is deleted automatically
* Append-only audit log with tamper detection
* Snapshot, verification and automatic rollback for every change the plugin applies
* A durable rollback history for settings changes — preview exactly what an undo would change, then undo it
* Local backups with a restore rehearsal in the same operation — a backup is only reported as taken once it has been read back
* TLS certificate monitoring: expiry, chain and hostname, read from your own server
* SMTP sending with the server certificate actually verified, plus an hourly cap so a compromised site cannot quietly become a mail relay
* Cache manager: detects the page cache in front of your site, purges what it can reach, and audits the purge
* A cache-poisoning check that measures whether your site reflects an unkeyed header into a cacheable response — and refuses to run the test when running it would poison your own cache
* Database maintenance with a preview, a transaction, and counts that have to match
* Redirect manager with loop detection and a dry run
* Server rule manager (.htaccess) with a structural pre-check and a live baseline read before anything is written
* Autoload audit: what your site loads on every single request, and what it costs
* PHP limits and environment profile — read and reported, never changed

**Honest capability reporting**

Every module reports one of: active, partial, observing, unsupported, or no
permission — with the reason. The Coverage Report never says "35 modules active."

**On the roadmap — not in this version**

The Coverage Report shows these honestly as not yet available, so you are never
misled: vulnerability (EPSS/KEV) scanning, and comparing core files against
official WordPress checksums. Our own audit on 2026-09-04 found that the
critical-file watch was in the same state — shipped, tested, and scheduled by
nothing. That half now runs daily; the core-checksum half does not, and the
Coverage Report says so rather than "awaiting first scan", which is what it
used to say about a scan that was never going to come. The
malware and ad-injection scans run on a schedule here; their real-time versions,
off-site scanning workers and live threat feeds are part of the paid Mintwall
Cloud.

**External services**

The free plugin makes exactly two kinds of outbound request on its own — (1) and
(2) below. The third is paid-tier only and is listed anyway. The fourth happens
only if YOU connect this site to a control plane, and never before. Nothing else
leaves your site.

1. Your own site. Some checks connect back to your own hostname to measure how
   your server actually behaves: the firewall self-test, the cache and header
   checks, the cache-poisoning check, and the TLS certificate monitor. The certificate monitor opens one
   TLS connection a day to your own https port, reads the certificate your
   server presents (expiry, chain, hostname) and closes the connection. It
   sends no data, no credentials and no cookies, and it contacts nobody else.
   Because it has to be able to report on a certificate your site cannot
   validate — expired, wrong hostname, self-signed — it reads that certificate
   instead of trusting it, and it refuses to connect to any host other than
   your own.

2. api.wordpress.org, for official WordPress core checksums, and only when you
   run a file-integrity scan. This sends your WordPress version and locale,
   which is what the checksum API needs to answer.

3. Your DNS resolver, for the SPF / DKIM / DMARC advisor (paid tier only). Once
   a day it asks your host's own resolver for the public records published for
   your own domain: the TXT records at your domain, at _dmarc.<your domain>,
   and at a short list of well-known DKIM selector names. It discloses nothing
   but your own domain name, which that resolver already resolves constantly,
   and it writes nothing anywhere — it can only advise. On a site with no paid
   licence this lookup is never made at all.

This plugin collects no usage telemetry: no counts, no timings, no samples, and
no "anonymous statistics". The only thing that leaves this site for us is what
item 4 of "External services" lists, and only after you have paired the site with
a control plane. Unpair and it stops.

There is no separate telemetry checkbox and that is deliberate. Pairing IS the
opt-in: you fetch a token from your own control plane and paste it in, which
WordPress.org's guidelines name as a consent method and which is a more specific
act than ticking a box. A second checkbox governing data the service cannot
function without would be a checkbox nobody could freely decline. If this plugin
ever collects something the service does not need, that will get its own switch,
off by default.

You can switch the checks in (1) and (3) off. There is a switch on the Coverage
Report screen: "Checks that leave this site". Turning it off is a settings change
like any other here, which means it is snapshotted and can be rolled back.

The plugin also honours WP_HTTP_BLOCK_EXTERNAL in wp-config.php, including
WP_ACCESSIBLE_HOSTS and the block_local_requests filter, exactly as WordPress
core does — note that core, and therefore this plugin, does not treat a request
to your own hostname as external, so use the block_local_requests filter if you
want those stopped too. The DNS lookups in (3) are external either way and are
stopped by WP_HTTP_BLOCK_EXTERNAL on its own.

When either one stops a check, the Coverage Report says so and names which one,
rather than leaving a silent gap.

4. A Mintwall control plane, and only after you connect this site to one. There
   is no default address: nothing is sent until an operator pastes a pairing
   token, and pairing is the opt-in. Once paired, the site signs each request
   with a key generated during pairing and asks the control plane for work; it
   also verifies the signature on every answer and ignores any answer it cannot
   verify. What leaves the site is the site identifier, its capability summary,
   the results of commands the operator's own control plane issued, and a
   revision number when the site asks for the threat-feed updates its licence
   covers. Unpair from the same screen and the requests stop.

Mintwall Cloud (optional, paid) adds real-time threat feeds, vulnerability data,
off-site scanning workers, multi-site management and immutable backups. The
protections above never require it, and this site contacts no control plane
until you pair it with one.

== Installation ==

1. Upload the plugin or install it from Plugins > Add New.
2. Activate it.
3. Open Mintwall Security. The Coverage Report detects your environment and
   shows which protection level (L0-L2) your host allowed, and why.
4. Leave the plugin in observe mode for 24-72 hours to build a baseline.
5. Review the events, then switch the mode to Strict.

= Modes =

**Observe** (the default) records what the firewall would have done and never
blocks. Leave it here for a day or two and read the Event Log — it shows the real
traffic your site gets.

**Strict** blocks requests that match an unambiguous attack signature and records
everything else. It is called Strict because that is what it does: it blocks. It
does not present a challenge page.

A third, more aggressive threshold is deliberately **not** in this version. The
firewall runs before WordPress, so it sees a request but not who is making it or
what the page will do with it — it cannot tell an editor saving a post that
contains markup from an attacker injecting it. Lowering the bar without that
context rejected ordinary content: a YouTube embed, a comment quoting HTML. That
mode arrives in 1.1, once the guard can make the distinction. Until then the
Event Log records those lower-confidence matches so you can see them without
anything being blocked.

== Frequently Asked Questions ==

= Will this slow my site down? =

The firewall runs before WordPress loads and uses a precompiled rule bundle. Our
budget is under 20 ms added latency at p95 for a normal front-end request, and
the plugin measures the real number on your own site instead of asking you to
trust ours.

= Does anything stop working when I do not pay? =

No. The free plugin is complete and has no time limit, no nag-locked features
and no license check. Paid plans add cloud services, not dormant local code.

= Can it break my site? =

Every change is snapshotted first, then verified. If verification fails the
plugin rolls back automatically. Days later you can still undo a settings change
from the Snapshot screen, after previewing exactly what the undo would change. If
the pre-WordPress guard cannot confirm itself, it fails open rather than locking
you out.

= Does it send my data anywhere? =

Not until you pair it with a control plane, and then only what item 4 of
"External services" lists — nothing else, ever. Unpairing stops it. There is no
usage telemetry in this plugin at all.

= My cache is still serving the old page after a repair. =

After the plugin removes injected markup it purges what it can reach: the object
cache entry for the row it changed, and the common page caches (WP Rocket, W3
Total Cache, WP Super Cache, LiteSpeed, Nginx Helper) when one of them is active.
It never guesses: if it finds a page cache it cannot drive, or none at all, it
says so on screen instead of reporting success. Server-level and CDN caches are
outside what a plugin can clear, so clear those yourself.

If you run a cache the plugin does not know, hook it up:

`add_action( 'mintwall_cache_purge', function ( $context ) {
	// $context is array( 'kind' => 'option'|'post'|'site', 'id' => '...' )
	my_cache_purge_everything();
} );`

The action fires after every repair, whether or not any built-in adapter matched.

== Screenshots ==

1. Coverage report — what is protected, what is not, and why
2. Firewall event log with rule id, reason and decision
3. Snapshot and rollback history
4. Two-factor setup

== Changelog ==

= 2.4.0 =
* Pre-WordPress firewall ladder with a WordPress-specific signature rule set, running at PHP's entry point before WordPress loads.
* Two-factor login (TOTP, recovery codes, passkeys), login/bot/enumeration protection and REST/AJAX rate limiting on one identity gate.
* Security headers and CSP, hardening profiles, upload security with re-encoding, and a PHP execution block for uploads.
* Scheduled malware scanning over PHP's own token stream, ad-injection / DB-DOM scanning, core checksum verification and drop-in monitoring.
* A daily system hardening audit: dangerous capabilities, registration role, critical constants, file permissions, orphaned cron, weak administrator passwords and end-of-life PHP.
* Quarantine with atomic moves and no automatic deletion, an append-only audit log with a hash chain, and a snapshot/rollback engine for every change.
* A Coverage Report that states, per module, what was measured and why — nothing unmeasured is reported as active.

== Upgrade Notice ==

= 2.4.0 =
No upgrade steps. Activation learns a baseline on its first run and never moves it forward on its own.
