WP Manifestindependent plugin directory
manifest / security / wp-perf-shield

WP Perf Shield

Blocks wp-perf-analytics traffic-hijacking malware and renamed variants with activation blocking, scheduled scanning, forensics, hardening, and remediation tools.

★ 0stars

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/menj/wp-perf-shield/archive/refs/heads/main.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Blocks wp-perf-analytics traffic-hijacking malware and renamed variants with activation blocking, scheduled scanning, forensics, hardening, and remediation tools.

Description

WP Perf Shield is a focused WordPress security plugin built to detect and disrupt the wp-perf-analytics / session-manager malware family and related ClickFix render-hijacker variants such as native-render-toolkit, total-render-profiler, total-render-toolkit, pro-font-optimizer, site-speed-insights, advanced-asset-insights, page-seo-toolkit, starter-image-guard, auto-content-profiler, pro-cache-scanner, and total-database-optimizer.

It has grown well past that starting point. Detection now covers doorway kits, obfuscated packers, file-operation web shells that contain no executable code at all, PHP configuration files that switch hardening back off, and cloaked injectors that hide from administrators and crawlers alike while serving content to ordinary visitors. Source is normalised before matching, so malware that splits its identifiers across concatenated fragments to defeat scanners is caught anyway.

Beyond scanning, the plugin records what happens on the site - sign-ins, account creation, role elevation, plugin and theme activity, uploads - grouping related activity into incidents with a cumulative risk score.

It combines real-time plugin activation blocking, hourly malware scanning, behavioural monitoring, a tamper-evident event log, one-click remediation actions, forensic reporting, and hardening helpers for common post-compromise cleanup tasks.

Key features

  • Blocks known malicious plugin slugs and renamed plugin patterns from activation.
  • Enforces a site-policy banned-plugins list, separate from malware blocking, for ordinary plugins you choose not to run (WP File Manager and FileBird by default): refused on upload and activation, and deactivated if already present.
  • Checks plugin file hashes using built-in MD5 indicators and custom MD5/SHA-256 hashes.
  • Scrubs blocked plugins from both normal active plugin lists and multisite network-active plugin lists.
  • Detects wp-content drop-in persistence loaders that restore mu-plugins/session-manager.php from wp_session_tokens_config.
  • Scans plugins, themes, uploads, cache directories, language directories, MU plugins, wp-login.php, wp-cron.php, wp-config.php, .htaccess, cron hooks, and suspicious database options.
  • Runs an hourly scheduled scan and sends rate-limited email alerts for critical findings.
  • Provides one-click cleanup actions for known credential harvesters, exfil files, malicious options, tampered wp-cron.php, suspicious users, attachments, plugin folders, and theme files.
  • Detects and cleans known removable malware patterns from wp-config.php with a pre-write backup and clean-baseline reset.
  • Includes forensic reporting for media uploads, admin accounts, plugin timestamps, theme tampering, option anomalies, PHP backdoors, and WordPress core file integrity.
  • Includes hardening actions for wp-config.php constants, .htaccess marker blocks, transient cleanup, session invalidation, and auth salt rotation.
  • Stores structured logs in a PHP-guarded file under wp-content/plugins/wp-perf-shield/logs.
  • Uses a modern minimalist admin UI with rounded panels, gradient accents, and mobile-friendly layout behavior.
  • Enqueues admin CSS and JavaScript from assets/css/admin.css and assets/js/admin.js.
  • Auto-deletes confirmed malware artifacts by default, with a Settings tab control for detect-only operation.
  • Auto-blocks IP addresses that attempt to upload known malware ZIPs or renamed ZIPs containing known malicious folders, hashes, or payload markers.
  • Protects sign-in against brute-force and low-and-slow subnet rotation: per-address and per-range thresholds with escalating repeat-offender blocks, permanent bans for attempts on non-existent accounts, a manual permanent range ban, XML-RPC multicall stripping, and optional Akismet reputation input and reporting — all without ever making the sign-in path wait on the network.
  • Detects file-operation web shells by capability cluster - browsing, writing, deleting and uploading driven by raw request parameters - which contain no eval or shell_exec and so evade execution-focused scanning.
  • Detects php.ini and .user.ini files that re-enable shell execution or remove open_basedir and disable_functions restrictions.
  • Detects cloaked injectors that serve content to visitors while hiding from logged-in administrators and from search-engine, SEO and page-speed crawlers.
  • Normalises split-literal obfuscation before matching, so identifiers written as glued fragments are found and every existing indicator keeps working.
  • Blocks outbound requests carrying WordPress session cookies to external hosts, and quarantines the files responsible.
  • Optionally blocks external post creation, editing and deletion through the REST API and XML-RPC (the auto-blogging and doorway-spam injection route), allowing only genuine dashboard publishing; off by default.
  • Records behaviour and groups related activity into incidents with cumulative risk scoring.
  • Renders its own documentation inside wp-admin.
  • Autoloads its classes, so an ordinary front-end page view parses a fraction of the codebase.
  • Includes a Diagnostics tab for attack attempts, hostile IPs, clearances, scan outcomes, Source Trace timeline, and operational status.
  • Records upload-pathway diagnostics and restricts ZIP uploads to trusted administrator upload routes.

Detection is described by technique rather than by malware name, because names change on every build and techniques do not: obfuscated and self-extracting payloads, identifiers spelled character by character to keep them out of the file, unauthenticated file managers, executable PHP in directories that should hold only data, configuration dropped to weaken the server, and credentials or session cookies being sent off-site. That last one is also blocked at runtime, before the request leaves.

Findings from a kit that plants the same file in many directories collapse into one entry listing every location, so an intrusion reads as one problem rather than a dozen.

Installation

  1. Upload the wp-perf-shield folder to /wp-content/plugins/.
  2. Activate WP Perf Shield from the WordPress Plugins screen.
  3. Open Tools > WP Perf Shield.
  4. Run a manual scan from the Overview tab.
  5. Review the Diagnostics, Forensics, Remediation, Hardening, Events, and Settings tabs as needed.

The plugin logs directory, wp-content/plugins/wp-perf-shield/logs/, must be writable by WordPress for event logging.

The PHP ZipArchive extension is recommended so WP Perf Shield can inspect inside renamed ZIP uploads before WordPress accepts them.

Additional project documentation is stored in the plugin doc/ folder. The root readme.txt remains in place for WordPress plugin metadata.

Frequently Asked Questions

Is this a general-purpose antivirus plugin?

No. WP Perf Shield is intentionally focused on known wp-perf-analytics / session-manager style malware, related backdoors, suspicious persistence options, and common post-compromise cleanup workflows.

Does deactivating the plugin remove protection?

Deactivation stops the normal plugin hooks and scheduled scan. The plugin intentionally does not remove protective artifacts that may need to survive deactivation unless removed through the admin UI or full uninstall.

Where are logs stored?

Security events are stored in wp-content/plugins/wp-perf-shield/logs/events.php. The file is PHP-guarded to prevent direct web exposure, and the log directory includes an index.php and .htaccess to block direct access.

Can I add custom indicators?

Yes. Use the Settings tab to add custom blocked slugs and custom MD5 or SHA-256 file hashes.

Does this support multisite?

The blocker checks and scrubs both normal active plugins and network-active plugins. Site-specific behavior should still be tested in your own multisite environment before relying on it broadly.

Can the plugin repair every infected file automatically?

No. Some repairs require SSH, WP-CLI, SFTP, or hosting-panel access. The plugin provides manual commands and targeted actions where automatic cleanup would be unsafe.

Screenshots

  1. Overview with scan status, issue summary, readiness checks, and recent events.
  2. Diagnostics tab with attack, clearance, scan, IP, Source Trace, and operational summaries.
  3. Forensics report with trace data and manual SSH commands.
  4. Remediation tab with targeted cleanup actions.
  5. Hardening checklist for post-compromise security controls.
  6. Events tab for the full retained security log.

Changelog

1.4.91

Adds the operator control for the remediation veto introduced in 1.4.88, which until now had no interface: the plugin could refuse to remove a target but no one could tell it to. Every finding gains a Mark Safe button with file or folder scope, and Diagnostics gains a panel listing everything protected, with its scope, reason and date, where paths can also be protected directly before they are ever flagged and any decision revoked later. Protection applies to every check including confirmed signature matches, since an operator is a better authority on their own code than any heuristic; findings remain visible and reported, but nothing acts on them automatically. Also adds a control to clear the automatic-removal halt, which previously required database access. This supersedes the 1.4.88 advice to disable automatic deletion.

1.4.90

Calibrates automatic removal without reducing detection. Behavioural findings about files inside installed plugins, themes and mu-plugins are now reported for operator review rather than removed automatically, which is the rule that would have prevented the earlier outages. Confirmed signature matches are still removed, and behavioural findings in locations where executable PHP has no legitimate reason to exist - uploads, cache directories, plugin temp and log folders - are still removed, since a false positive there costs a stray file rather than a working site. WordPress core is never removed automatically and an operator Safe decision overrides everything. Also fixes the Overview screen, which hid the manual removal button for exactly those findings the policy had declined to act on, leaving them visible but unactionable; they now show a "Needs your review" status alongside a working control.

1.4.89

Fixes WordPress core files being quarantined. A genuine wp-admin/setup-config.php was reported as critical credential exfiltration because it reads the database password field, which is its purpose as the installer; content heuristics now skip core entirely and leave core judgement to the existing checksum verification against the official WordPress API, which answers the question exactly rather than by inference. Also fixes the core protection introduced in 1.4.88, which only applied to detectors named on a fixed heuristic list and so did not cover this or any detector added later; the list is inverted to name confirmed signature detectors instead, so anything unrecognised is treated as inference and fails safe. Core is now never removed automatically regardless of detector confidence, since deleting a core file breaks a site rather than cleaning it, and the definition of core was widened to include index.php, xmlrpc.php and other files that do not begin with wp-.

1.4.88

Critical remediation-safety release. Earlier versions could automatically quarantine legitimate software and repeat that action on subsequent scans even after an operator approved the file, because detection authorised removal directly and no operator veto existed. All destructive action now passes through a central remediation policy. Marking a target safe is an absolute veto on automatic removal, stored persistently, keyed by canonical path identity so it cannot be defeated by a different detector, a new finding ID, or an alternative spelling of the path, and checked again immediately before anything is moved. The policy fails closed when trust state cannot be read, refuses to let behavioural findings remove entire plugins or WordPress core files, and records Performance Lab, WP-Optimize, Abstract Box and Auto-justify Content as known-legitimate. If a safe target ever reaches the destructive gate, all automatic removal halts and the administrator is notified. Note: the dashboard control for marking targets safe is not yet included; until it ships, operators who need to protect specific files should disable automatic deletion in Settings.

1.4.87

Strengthens the database-resident payload check against trivial evasion. Adversarial testing of a known-caught sample showed that two simple mutations - joining the deliberately split identifiers, and moving the encoded payload out of the source into a sibling file - each survived individually but together evaded every check, because the two detections that appeared to confirm one another in fact depended on exactly one precondition each. The check now keys on the mechanism the loader cannot drop: a decoder applied to get_option, the same option written back, and the result placed where it executes. The payload's physical location is no longer relevant. Verified against three legitimate option-decoding plugins, which remain unflagged, and covered by a new adversarial harness that rebuilds the mutations from a real sample on every run.

1.4.86

Adds detection for plugins that store their executable payload in a WordPress option rather than in their own files, writing it back whenever the option is missing. Three new samples of the random-suffix packed family use this, and while all three were already detected by the split-identifier check, the existing finding did not tell the operator that deleting the plugin folder leaves the payload behind in wp_options for the next loader to find. The new check requires a hardcoded encoded blob, an update_option writing it and a get_option reading it back, names the option in the finding, and quarantines it reversibly alongside the folder. Also fixes a defect in that check found during development, where one throwing file aborted the entire directory walk and caused every later file to go unreported.

1.4.85

Extends Akismet reporting to two cases that were going unused. Confirmed injected gambling and SEO-spam comments carry the sender's address in comment_author_IP, which is Akismet's canonical case, and those authors are now reported (capped per scan). Separately, when a scan confirms malware, the plugin looks for an address in its own hostile-IP records that was active when that file was written - the file's modification time against the recorded activity window plus a margin - and reports it with the timing stated. This is deliberately narrow: only addresses already blocked for hostile behaviour are considered, the timing must line up, at most one address is blamed per file, ranges are never submitted, and no timing match means no report rather than a guess. Note that Akismet accepts addresses, not file hashes, so malware itself cannot be submitted; what is reported is the address responsible where evidence exists.

1.4.84

Repairs the control-flow-flattening check, which read a real packed backdoor as clean. The sample used 121 goto jumps across four lines and sent data to a Telegram bot, but wrote every string in hex and octal escapes, so the two tells that would have proven intent - a readable execution sink and a long encoded payload run - both scored zero, leaving it one tell below the reporting floor. The payload and execution tells now run against escape-resolved text, which this plugin has been able to produce since 1.4.34 but this check never used. Adds a sixth tell for a messaging-bot or webhook endpoint that is hidden in the raw file and only appears once escapes are resolved; a plainly visible webhook is ordinary and is not counted.

1.4.83

Adds a plugin roster that reports any plugin folder appearing without an installation being recorded through the dashboard or WP-CLI, regardless of whether the plugin's own code is clean. This addresses a real gap: an intruder frequently installs a legitimate, working tool such as a file manager because it draws less attention than a web shell and matches no malware signature, so every content-based check correctly returns clean while the site remains compromised. The presence of an uninstalled plugin is evidence that someone other than the owner could place files on the server, and its folder date approximates when. The first scan adopts existing plugins as a baseline and reports nothing, so upgrading does not produce a wall of false alarms; afterwards, anything new is reported once. Detection only, since a plugin may legitimately have been installed by SFTP.

1.4.82

Fixes a gap where the encoded-payload-loader check reported backdoors without removing them, leaving a live remote-code loader on disk while describing it accurately in the scan report. That check now requests removal like every other check of comparable severity, through the same quarantine-first path so the action stays reversible and the evidence is preserved. It removes only the offending file, never the surrounding directory, because these loaders are typically planted among legitimate files in wp-content/uploads; loaders that are part of a purpose-built malicious plugin continue to be removed folder and all by the plugin-level checks.

1.4.81

Adds detection for hidden administrator backdoors: code that creates an administrator account and then conceals it from the Users screen and the REST API, typically recreating it after deletion and writing its credentials in escape codes to defeat searching. Both halves are required before anything is reported - creating an administrator is legitimate, hiding one is not - so genuine user-management tools are unaffected. The finding gives the remediation order explicitly, since deleting the account before removing the code simply restores it. Also repairs two evasions in the encoded-payload check found in a live sample: a payload stored as raw bytes after the closing PHP tag rather than in a quoted string, and an execution sink split apart by comments and prefixed with the error-suppression operator.

1.4.80

Adds detection for two further techniques from the ongoing campaign: code broken apart by junk comments inserted between tokens (which runs normally but defeats searching, and additionally uses random capitalisation against case-sensitive signatures), and tiny typosquat plugins whose only behaviour is injecting a remote script into every page. The comment-obfuscation check strips comments and compares against the raw text, flagging the case where dangerous calls appear only after stripping - an honest file shows no such gap. Because this family plants files inside genuine unmodified plugins, that finding removes the planted file rather than the surrounding plugin and says the rest of the folder should be verified against the official source. Also repairs the opaque-data-payload check, which analysed one file at a time and so missed a variant that named its data blob in one file and read it in another; evidence is now gathered across the whole plugin.

1.4.79

Adds detection for a packed fake-plugin family that defeated every existing content check (measured, not assumed - the harness asserts the pre-1.4.79 checks find nothing on the samples). These plugins install under plausible names with a random four-hex suffix and keep their behaviour in opaque data files rather than PHP, so code-reading scans see a trivial clean plugin. Two new structural checks: one flags identifiers split across concatenation to defeat search (requiring a mid-word split, a density threshold, and that joining the pieces reveals a real API name), and one flags a plugin that reads an opaque data blob and either writes PHP out of it or hands it to a decoder or executing sink. One recovered variant self-reconstructs its PHP from an XOR-encrypted data file when the PHP is deleted, so removal targets the whole folder. Ordinary concatenation, binary font assets, and binary caches that are read but never executed are all left alone.

1.4.78

Fixes two defects in the encoded-payload-loader check, found by running a live sample against the shipped code. A planted loader inside a legitimate Filester install - seven directory levels deep in the plugin's own upload area, chaining eval over htmlspecialchars_decode, base64_decode and urldecode - returned zero findings. The directory walk stopped at depth 5, and only one of the loader's four decode layers was on the list the check counts, leaving it under the two-layer floor. The payload scan now walks to depth 10 via a named constant, and transport decoders (urldecode, rawurldecode, html_entity_decode, htmlspecialchars_decode, stripslashes) count as layers. Also confirms two other uploads needed no new work: a repeat of the .htaccess-injected WP File Manager copy already covered in 1.4.76, and an empty directory left behind by a deleted ClickFix-family plugin.

1.4.77

Adds detection for three malicious artefacts recovered from a compromised site: the VeyronHacklink backlink-injection family (Turkish SEO hacklink kit that pulls links from a master server, injects them into the footer, and self-promotes into mu-plugins to survive plugin deletion), a fake "XDav Tracker" plugin that hides itself from the Plugins list and serves obfuscated script only to non-administrators, and two unauthenticated PHP file managers buried inside a homoglyph typosquat of WPForms Lite. Beyond named signatures, adds a structural check for self-concealing plugins: any plugin that removes its own entry from the Plugins list, or withholds its front-end output from administrators while serving it to visitors. Neither behaviour has a legitimate use, and the check survives a variant rewriting its payload. Verified against the real samples.

1.4.76

Detects web-shell persistence hidden in .htaccess, a compromise class where no PHP file is modified and code-based scanning returns clean. Flags any .htaccess that denies PHP while allowlisting PHP filenames WordPress does not ship - recovered from a live case of 188 identical files injected through a plugin tree, allowlisting about.php, radio.php, content.php and lock360.php. Detection is structural, so a variant that renames its shells is still caught, and findings are grouped into one incident rather than one per file. Ordinary hardening (plain Deny from all, WordPress-only allowlists, the standard rewrite block) is not affected. Adds lock360.php and radio.php as named signatures under a new family label.

1.4.75

Completes the site-policy banned-plugins feature: a banned plugin found installed on disk is now quarantined and removed, not merely blocked from upload and activation. Prompted by a live sample of WP File Manager whose own files were byte-identical to the clean release but which carried 188 attacker-written .htaccess files through its folder tree, allowlisting a fixed set of webshell filenames - a persistence mechanism signature scanning alone would miss. Removal is quarantine-first and reversible, honours the auto-delete setting, is gated on the banned-plugins switch, matches by exact folder name so similarly-named plugins are untouched, and is recorded as a site-policy decision rather than a malware detection, since these plugins are legitimate software.

1.4.74

Adds detection for the "Dark X7ROOT File Manager" web shell, found live this session disguised as the root index.php of a genuine, unmodified plugin folder. Two independent checks: a named signature for immediate identification, and a structural check that catches the disguise technique itself - an oversized, unauthenticated, request-handling index.php at a plugin/theme root sitting beside multiple genuine near-empty stub index.php files, which is how WordPress plugins normally look at every folder level. The structural check is conservative and tested against both a synthetic tree and the actual captured sample; it does not fire on a real plugin bootstrap file, a guarded file, or a large file that takes no request input.

1.4.73

Adds detection of injected casino/gambling/SEO-spam content in posts and comments - the content side of an auto-blogging/injection compromise, which the file and option checks miss. A shared signature matcher flags SEO-spam tokens (slot gacor, rtp live, maxwin, togel) outright, and ordinary gambling words only in bulk with hidden markup or a link wall, so it does not flag legitimate writing that mentions gambling. A new scanner check finds spam already published (with sample IDs, detection-only - it never deletes), and a real-time save_post hook flags new injections whatever the vector. Includes entry-point-first guidance so removing content does not simply invite republication.

1.4.72

Extends Akismet reputation-weighted block duration to the other guards. A malware-uploading address that Akismet also knows as bad is now held 30 days instead of 7 (a clean answer never shortens it, since a malware upload is already conclusive). A sustained external-post-injection campaign (10+ blocked writes from one address within an hour) escalates from a per-request refusal to a persistent hostile-IP block, 1 day or 7 when known-bad; the high threshold, an infrastructure-address exemption, and the hostile-IP auto-block switch keep it from misfiring. The reputation lookup is shared and cached; enrichment remains a duration input only, never consulted on the request path. Also generalises the blocked-request message so it is accurate for every block reason.

1.4.71

Extends Akismet reporting to the other conclusive-abuse guards: an address caught uploading malware or injecting posts through the REST API is now reported to Akismet, alongside failed sign-ins, through one shared safeguarded path (never a CDN/proxy address, never a range, once per address). Governed by the existing Report attackers to Akismet switch. Also a full tightening pass across the merged codebase.

1.4.70

Merges the standalone Block External Posting plugin as a new module: blocks external post creation, editing and deletion through the REST API (/wp/v2/posts) and the post-writing XML-RPC methods unless the request is a genuine administrator dashboard session, which no Application Password, Basic Auth, JWT, OAuth or unauthenticated client can pass. This is the injection route behind auto-blogging and doorway/SEO-spam posts. Adds logging of blocked attempts (throttled per address), which the standalone lacked. Off by default because it breaks headless, mobile-app and Zapier-style publishing; enable under Settings only if nothing legitimately posts from outside the dashboard. Gutenberg, Classic Editor and scheduled posts are unaffected.

1.4.69

Documentation sync, no code change. Brought doc/readme.md and the readme.txt Description forward to cover the features added since 1.4.61 (banned-plugins denylist, event-chain self-test, subnet-rotation trigger and range escalation ladder, XML-RPC multicall stripping, manual permanent range ban, report-every-blocked-address); their content had drifted while only the version line moved.

1.4.68

Adds a one-click permanent range ban (Diagnostics) for permanently denying an address or whole subnet, refused if it holds your own or an admin address or is broader than a /16. Also adds a default-on control to report every blocked address to Akismet, not only conclusive ones, and to report the individual attacking members of a blocked range — never a CDN/proxy address and never a whole range, so innocent neighbours are not flagged; each address is reported at most once, and it has an off switch. The posture panel's blocks-by-rule line now attributes single-address blocks so the totals reconcile.

1.4.67

Repeat attacking ranges now escalate. A rotating /24 was previously held for a flat six hours and could return the moment it lifted; range blocks now climb a repeat-offender ladder (6 hours, 24 hours, 3 days, 7 days) over a 14-day memory, in a store separate from the per-address one, so a persistent subnet locks itself out for longer each time. It caps at seven days — a permanent ban of a whole range stays a manual decision, since a /24 can hold people who are not the attacker — and the allowlist and remembered admin addresses are never blocked. Also fixes the Security posture panel, which showed only range-rotation blocks and hid the hundreds of single-address blocks; the "blocks by rule" line now attributes all four categories so the totals reconcile.

1.4.66

Closes the XML-RPC credential-amplification vector without breaking normal XML-RPC sign-in. Failed XML-RPC sign-ins were already counted by the login guard and blocked addresses were already rejected on xmlrpc.php, but system.multicall — which lets one request carry many credential guesses — was only removed when XML-RPC sign-in was disabled wholesale (off by default, to protect Jetpack and the mobile apps). Since those clients use direct methods rather than system.multicall, the plugin now strips system.multicall on its own, on by default, closing the vector while leaving Jetpack and the apps working. Switchable under Settings for the rare tool that batches through system.multicall on purpose.

1.4.65

Stops low-and-slow subnet brute-force. When an attacker rotates a /24 — one failed sign-in every several minutes from a different address each time — no single address reached the per-address threshold, no short window reached the network threshold, and the old tally reset between attempts. The network guard now treats rotation itself as the trigger: five distinct failing addresses in one /24 within a one-hour rolling window blocks the range, whatever the pacing, and holds it for six hours. Every safeguard is intact — a range holding the allowlist or a remembered administrator address is never blocked, and a single address failing repeatedly (a real user mistyping a password) never causes a range block. On-by-default with its existing off switch.

1.4.64

Adds an in-plugin Event-chain self-test (Diagnostics) that verifies the CRIT-005 concurrency fix against the live database on the host, with no WP-CLI or shell scripts. It runs the real append path over an isolated scratch table that is dropped afterwards (the real chain is never touched, and isolation is asserted), and opens a second database connection to prove the append advisory lock excludes across connections. Reviewed as third-party code before inclusion: no execution or network primitives, the scratch namespace is validated so it cannot inject SQL, and the teardown can only drop a scratch table, never the real one. Run it once on staging after upgrading.

1.4.63

Fixes CRIT-005: the tamper-evident event log's append was not concurrency-safe, so two simultaneous requests could fork the hash chain and the verifier would report the fork as tampering — a false alarm from ordinary traffic. The append is now serialised with a database advisory lock (with a transaction fallback), and the chain head is read from the table rather than a cached value, which closes the race and self-heals a previously-forked cached head. Preventive, not retroactive: it stops new forks but does not repair a chain already forked before upgrade; re-run chain verification after upgrading. Build-time logic checks pass; real-database concurrency should be verified on staging before production.

1.4.62

Adds a site-policy plugin denylist, separate from malware blocking, for refusing ordinary plugins an operator does not want running. Two plugins ship banned by default: WP File Manager, which grants full dashboard filesystem access and carries a history of critical remote-code-execution holes, and FileBird. A banned plugin cannot be uploaded or activated while WP Perf Shield is active, and is deactivated if already running. These are recorded as policy decisions, not malware detections, so the tamper-evident event log stays honest, and a policy refusal never adds the uploader's address to the hostile-IP block list. Manage the list, and add your own slugs, under Settings and then Banned plugins; the whole list has an on/off switch and is on by default.

1.4.61

Adds a remediation roadmap covering the outstanding security-review work, readable in the Docs tab. It states which of the twenty findings are fixed, which remain, what to do next with acceptance criteria, and the conventions and pitfalls someone resuming the work would otherwise have to rediscover. The roadmap is checked against the code by the test suite: anything it claims is fixed must genuinely be present, and anything it lists as outstanding must genuinely still be absent, so the document cannot drift from reality unnoticed. Two overstatements in the 1.4.60 notes are corrected - per-check execution status was added to one function rather than across the scanner, and a misleading message about manual scans is still present. No functional code changed.

1.4.60

Phase one of an external security review. Fixes a defect that made the WordPress.org plugin-integrity check dead on arrival - it iterated an undefined variable, so a tampered plugin file and a planted backdoor both reported clean. Every result from that check now states whether it actually ran, because a silent failure and a clean result were previously indistinguishable. Stops automatic remediation deleting a file when quarantine fails: a quarantine failure means the machine is misbehaving, not that destroying the file is safe, so the threat is now left in place and reported instead. Adds a proper single-flight lock so scheduled, manual and post-upgrade scans can no longer run simultaneously and remediate each other's findings. Akismet reporting remains enabled by default as intended; an inaccurate comment describing it as opt-in is corrected, and a second Akismet control that was read but could never be set now has a real settings row. Two further critical findings remain outstanding and are next.

1.4.59

Documentation only; no code changed. A supplied sample confirmed an existing catalogue entry exactly - both fingerprints already held and both matching the file - so the entry is promoted from recorded to verified. Auditing the hash list to check it turned up that 17 of 34 fingerprinted samples carry an MD5 with no SHA-256 companion, and one carries no fingerprint at all. The variant catalogue now publishes exactly where that coverage is thin, generated from the blocking list and checked against it by the test suite. This is a corroboration gap rather than an evasion hole: altering a file changes both hashes, so a single fingerprint is no easier to evade than a pair.

1.4.58

Adds file fingerprints for the fourth member of the theme-loader JavaScript injector family, supplied after 1.4.57 identified it as documented in code comments but never catalogued. Its size matches the byte count recorded during the original analysis exactly. Three of the four known members are now verified from files in hand. Blocking and detection are unchanged - this family has been blocked by folder shape rather than by name since 1.4.50, and the sample was already blocked at activation and upload before this release.

1.4.57

Adds fingerprints for a newly supplied sample of the theme-loader JavaScript injector family, and corrects the bundled variant catalogue, which listed two members of that family where the source knew four. The two missing members were recorded in code comments during the original analysis and never reached the catalogue. Blocking was never affected - since 1.4.50 this family is blocked by its folder shape rather than by name, and the new sample was blocked at activation and upload by a plugin that had never seen it. The completeness check that should have caught the documentation gap only read values from indicator lists, so it could not see a sample named in a comment; it now extracts sample identifiers from the source too, and that change was verified by injecting an undocumented identifier and confirming the check fails.

1.4.56

Identifies the plugin's founding target. Sekoia's analysis of the ErrTraffic malware-as-a-service framework names a Polygon smart contract that WP Perf Shield has carried in its source since the original wp-perf-analytics work - the same address, confirming that the campaign this plugin was written against is a commercially sold framework rather than an anonymous operation. Adds detection for on-chain command-and-control by technique rather than by endpoint list, which matters because one cluster uses per-customer RPC subdomains and per-affiliate contracts that cannot be enumerated in advance. Also fixes a false positive found while testing it: public blockchain RPC hostnames and the eth_call method name were single-match malware signatures, so any legitimate NFT, wallet or web3 plugin would have been reported as malware and offered for deletion. Detection of real samples is unchanged.

1.4.55

Fixes two security defects found in an external review. Until now, every wp-config.php edit wrote a backup named wp-config.php.wps.bak beside the original - a file the web server hands out as plain text, exposing database credentials and authentication salts to anyone who requested it. Backups now go to quarantine, and the edit is refused rather than performed unsafely if quarantine is unavailable. Because upgrading cannot remove copies already written, a new critical check reports any readable wp-config backup in the web root, including ones left by editors, hosts and other tools. Separately, saving the Settings tab replaced the whole options record and silently reverted Content-Security-Policy to off; it now merges, so settings owned by other tabs survive. If you have used the Hardening tab before, scan after upgrading and treat any reported backup as a disclosed credential.

1.4.54

Adds a brand mark. WP Perf Shield had no icon of its own, which blocked the technology-profiler submission prepared in 1.4.52 and left the plugin header unadorned. The mark is a shield containing three ascending bars - the shield for the category, the bars for the plugin's origin in performance tooling that malware impersonates. It is a 442-byte SVG using the interface's existing accent colour, drawn and checked at 16 pixels because that is the size directory listings actually render, and asserted free of scripts, event handlers and external references since browsers fetch it from wp-content. It now appears beside the title on the plugin screen. No detection or blocking behaviour changed.

1.4.53

Completes the blocking coverage for the auto-asset-helper malware variant. Releases 1.4.49 added its name and folder patterns but deliberately left out its file fingerprints, because the sample was not available to hash and a fingerprint written from memory matches nothing while looking like protection. The sample has now been supplied, so the fingerprints are computed from the file and the variant is covered across all five blocking layers. Detection is unchanged - it was already found by six separate checks - and no other behaviour changed.

1.4.52

Adds an optional way to let technology profilers such as Wappalyzer recognise WP Perf Shield. Until now the plugin was invisible from outside - its admin assets load only on its own screen - so no profiler could detect it. A new setting emits a sin

This README is longer than the copy stored here. Read the rest on GitHub →