{"id":364454,"date":"2026-09-30T10:00:49","date_gmt":"2026-09-30T10:00:49","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/safe-first-security\/"},"modified":"2026-09-30T10:00:29","modified_gmt":"2026-09-30T10:00:29","slug":"stillward-security","status":"publish","type":"plugin","link":"https:\/\/ur.wordpress.org\/plugins\/stillward-security\/","author":23561073,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"2.3.3","stable_tag":"2.3.3","tested":"7.1.2","requires":"5.0","requires_php":"7.2","requires_plugins":null,"header_name":"Stillward Security","header_author":"Adnan Ali","header_description":"Lightweight, safe-by-default WordPress security. Hardens your site (security headers, brute-force protection, login\/version\/enumeration hardening, upload protection) without interfering with normal site behaviour. Aggressive features are opt-in.","assets_banners_color":"132135","last_updated":"2026-09-30 10:00:29","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"","header_author_uri":"https:\/\/profiles.wordpress.org\/adnanali32038\/","rating":0,"author_block_rating":0,"active_installs":0,"downloads":31,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"2.3.3":{"tag":"2.3.3","author":"adnanali32038","date":"2026-09-30 10:00:29","revision":3720912}},"upgrade_notice":{"2.3.3":"<p>Repackaged release; identical in behaviour to 2.3.2.<\/p>","2.3.2":"<p>Activation no longer removes anything, auto-clean is off by default, and an\noption is only removed when its value carries a malware marker.<\/p>","2.3.1":"<p>Correct handling of installs that move or rename wp-content, the plugins\ndirectory or mu-plugins.<\/p>","2.3.0":"<p>Quarantined files move from disk into the database, restoring a removed file\nbecomes a download, and the file editor now follows the WordPress Hardening\nsetting.<\/p>","2.2.0":"<p>Adds database and account scanning, quarantine with one-click restore, and much\nstricter malware-removal gates. Recommended for all users.<\/p>","2.1.0":"<p>Malware Shield no longer removes a file on a generic heuristic alone.<\/p>","2.0.0":"<p>Major rewrite: safe-by-default, firewall is now opt-in and never edits requests.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3720899,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3720899,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3720899,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3720899,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["2.3.3"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3720979,"resolution":"1","location":"assets","locale":"","width":1920,"height":3210},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3720979,"resolution":"2","location":"assets","locale":"","width":1920,"height":900},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3720979,"resolution":"3","location":"assets","locale":"","width":1920,"height":520},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3720979,"resolution":"4","location":"assets","locale":"","width":1920,"height":977},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3720979,"resolution":"5","location":"assets","locale":"","width":1920,"height":1754}},"screenshots":{"1":"The Protection tab. Every feature carries a Safe, Opt-in or Advanced badge, and core protection is already on after activation.","2":"Malware Shield. Scan-only and scan-and-clean runs, plus a plain account of what a plugin alone cannot fix after an infection.","3":"Findings and quarantine. A clean install reports nothing at all, and anything the shield does remove is kept as a copy you can download again.","4":"Account-wide scan. Read-only: it looks at every site under the same hosting user and never changes anything outside this one.","5":"The activity log, and the full list of event types it can record."}},"plugin_section":[],"plugin_tags":[2439,1174,31093,1184,600],"plugin_category":[54],"plugin_contributors":[283652],"plugin_business_model":[],"class_list":["post-364454","plugin","type-plugin","status-publish","hentry","plugin_tags-brute-force","plugin_tags-firewall","plugin_tags-hardening","plugin_tags-malware","plugin_tags-security","plugin_category-security-and-spam-protection","plugin_contributors-adnanali32038","plugin_committers-adnanali32038"],"banners":{"banner":"https:\/\/ps.w.org\/stillward-security\/assets\/banner-772x250.png?rev=3720899","banner_2x":"https:\/\/ps.w.org\/stillward-security\/assets\/banner-1544x500.png?rev=3720899","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/stillward-security\/assets\/icon-128x128.png?rev=3720899","icon_2x":"https:\/\/ps.w.org\/stillward-security\/assets\/icon-256x256.png?rev=3720899","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/stillward-security\/assets\/screenshot-1.png?rev=3720979","caption":"The Protection tab. Every feature carries a Safe, Opt-in or Advanced badge, and core protection is already on after activation."},{"src":"https:\/\/ps.w.org\/stillward-security\/assets\/screenshot-2.png?rev=3720979","caption":"Malware Shield. Scan-only and scan-and-clean runs, plus a plain account of what a plugin alone cannot fix after an infection."},{"src":"https:\/\/ps.w.org\/stillward-security\/assets\/screenshot-3.png?rev=3720979","caption":"Findings and quarantine. A clean install reports nothing at all, and anything the shield does remove is kept as a copy you can download again."},{"src":"https:\/\/ps.w.org\/stillward-security\/assets\/screenshot-4.png?rev=3720979","caption":"Account-wide scan. Read-only: it looks at every site under the same hosting user and never changes anything outside this one."},{"src":"https:\/\/ps.w.org\/stillward-security\/assets\/screenshot-5.png?rev=3720979","caption":"The activity log, and the full list of event types it can record."}],"raw_content":"<!--section=description-->\n<p>Stillward Security follows one rule: <strong>protect, don't disturb.<\/strong><\/p>\n\n<p>Most security plugins break sites by aggressively filtering requests, stripping\nform data, whitelisting file types, or forcing strict policies. Stillward ships\nwith only <em>non-breaking<\/em> hardening enabled by default. Anything that can affect\nhow your site works is turned OFF until you knowingly enable it.<\/p>\n\n<p><strong>Enabled by default (safe on any site):<\/strong><\/p>\n\n<ul>\n<li>Security headers \u2014 X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy<\/li>\n<li>WordPress hardening \u2014 hide version, generic login errors, disable the file editor, remove head meta leaks<\/li>\n<li>Block username enumeration \u2014 stops ?author=N probes and the public REST users list<\/li>\n<li>Brute-force login protection \u2014 IP lockout after too many failed logins<\/li>\n<li>Upload protection \u2014 blocks executable uploads (.php, .exe\u2026) and disables PHP execution in \/uploads (normal media and documents still upload)<\/li>\n<\/ul>\n\n<p><strong>Opt-in (off by default \u2014 enable knowingly):<\/strong><\/p>\n\n<ul>\n<li>Disable XML-RPC (leave off if you use Jetpack or the WP mobile app)<\/li>\n<li>HSTS header (HTTPS-only sites)<\/li>\n<li>Content-Security-Policy (test carefully)<\/li>\n<li>Strict MIME whitelist<\/li>\n<li>Custom (hidden) login URL<\/li>\n<li>Request firewall \u2014 SQLi\/XSS\/traversal detection. It NEVER edits your submitted\ndata, runs in log-only mode by default, and only blocks if you switch it to\nBlock mode.<\/li>\n<li>Auto-clean for the Malware Shield. Scanning is on and reports what it finds;\nremoving anything automatically is your decision. You can also clean once, on\ndemand, with the \"Scan &amp; clean\" button.<\/li>\n<\/ul>\n\n<p><strong>Malware Shield \u2014 and why it will not eat your files<\/strong><\/p>\n\n<p>The Malware Shield hunts one specific, self-healing infection (fake <code>db.php<\/code> \/\n    advanced-cache.php drop-ins, <code>vapor-*<\/code> \/ <code>host-*-bridge<\/code> mu-plugins, injected\ntheme <code>functions.php<\/code>, <code>sc_*<\/code> options and cron). Deleting a legitimate file is\nworse than the malware, so three gates must all pass before anything is removed:<\/p>\n\n<ol>\n<li><strong>Confidence<\/strong> \u2014 only an exact marker unique to this malware family can lead\nto a removal. The generic \"obfuscated code\" heuristic is report-only, because\nlicence loaders, packers and minified libraries look exactly like that.<\/li>\n<li><strong>Location<\/strong> \u2014 removal is limited to the places this family actually drops\nfiles: the three wp-content drop-ins, mu-plugins, PHP files in <code>\/uploads<\/code>,\nthe <code>wp-content\/cache<\/code> staging copy, filenames it is known to plant, and any\nfile in <code>wp-includes<\/code>\/<code>wp-admin<\/code>\/the web root that the official WordPress\nchecksum manifest says WordPress does not ship.\nA file that IS part of a real plugin, theme or WordPress itself is <strong>never\ndeleted<\/strong> \u2014 it is reported instead, because an infected real file needs\nreinstalling, not erasing. <code>wp-config.php<\/code> is never deleted under any\ncircumstance.<\/li>\n<li><strong>Protected paths<\/strong> \u2014 this plugin's own folder and other security\/backup\nplugins (which legitimately ship malware signatures in their source) are\nskipped entirely.<\/li>\n<\/ol>\n\n<p>Everything that <em>is<\/em> removed is copied into the plugin's own database table\nfirst -- never into another file on the server, because a copy of malware on\ndisk is still malware on disk. If a removal turns out to be a mistake, download\nthe copy from the Malware Shield tab and put it back. Removed options and cron\nevents are backed up the same way and restore in one click. An infected theme\n    functions.php is never modified or deleted: it is reported, so you can\nreinstall a clean copy of the theme.<\/p>\n\n<p>Developers can force report-only behaviour for any path with the\n    wpss_malware_auto_removable and <code>wpss_malware_protected_paths<\/code> filters.<\/p>\n\n<p><strong>Database Audit \u2014 the things a file scanner cannot see<\/strong><\/p>\n\n<p>A file scanner is blind to a compromise that never writes a file, and that is\nnot a hypothetical: an SEO-cloaking campaign ran for four months across eight\nsites on one hosting account while hourly scans reported clean, because its code\nlived in a plugin's database table, its configuration in an option, its spam in\n    wp_posts, and its administrators were inserted straight into <code>wp_users<\/code>.<\/p>\n\n<p>The audit runs alongside the file scan and asks three questions that have exact\nanswers:<\/p>\n\n<ul>\n<li><strong>Is there an option named after this site's own hostname?<\/strong> The malware names\nits configuration row <code>md5(sha1($host))<\/code>, so the name is different on every\nsite and no blocklist can list it \u2014 but the same name can be computed here and\nlooked up. There is no false-positive surface at all. Autoloaded 32-hex option\nnames in general are raised as a warning.<\/li>\n<li><strong>Was any administrator created by something other than WordPress?<\/strong>\n  WP_User::add_role() assigns a boolean, so WordPress only ever writes\n  s:13:\"administrator\";b:1. A row built by hand in SQL writes a string\ninstead. That single difference found four rogue administrators across those\neight sites and produced no false positives. Duplicate <code>user_login<\/code> values are\ntreated the same way \u2014 WordPress will not create one.<\/li>\n<li><strong>Does any content belong to a user that does not exist?<\/strong> And were large\nbatches of posts written within a single second? A legitimate import looks\nidentical to an injection here, so both are warnings with a one-click \"It was\nme\" that stops that exact fact being reported again.<\/li>\n<\/ul>\n\n<p>Nothing found in the database is ever changed automatically. A confirmed rogue\nadministrator can be demoted to Subscriber with its sessions ended and password\nreset \u2014 never deleted \u2014 and the previous role, capabilities and password hash go\ninto quarantine first so Restore puts the account back exactly as it was.<\/p>\n\n<p>Critical findings are e-mailed the moment they are recorded: once per distinct\nfinding per day, at most twelve an hour, and only for findings that mean\nsomething got in. Routine lockouts and firewall blocks are logged but never\nmailed, because an alert that is always noise teaches people to ignore alerts.<\/p>\n\n<p><strong>Safety guarantees<\/strong><\/p>\n\n<ul>\n<li>The firewall only <em>reads<\/em> requests \u2014 it never modifies or strips POST data, so\nit cannot break form nonces or submissions.<\/li>\n<li>Deactivating the plugin is non-destructive: it does not touch wp-login.php,\n.htaccess, your options or logs.<\/li>\n<li>No per-request database writes \u2014 only real security events are logged.<\/li>\n<\/ul>\n\n<!--section=installation-->\n<ol>\n<li>Upload the <code>stillward-security<\/code> folder to <code>\/wp-content\/plugins\/<\/code>, or install\nthe ZIP via Plugins \u2192 Add New \u2192 Upload Plugin.<\/li>\n<li>Activate the plugin.<\/li>\n<li>Go to <strong>Stillward<\/strong> in the admin menu to review settings. Core protection is\nalready on; enable advanced features only if you need them.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"will%20this%20plugin%20break%20my%20site%3F\"><h3>Will this plugin break my site?<\/h3><\/dt>\n<dd><p>That is the one thing it is built not to do. Only non-breaking hardening is on\nafter activation. Every feature that can change how your site behaves -- the\nrequest firewall, Content-Security-Policy, HSTS, the strict MIME whitelist, the\ncustom login URL and XML-RPC blocking -- is off until you turn it on yourself.<\/p><\/dd>\n<dt id=\"the%20malware%20shield%20deletes%20files.%20how%20do%20i%20know%20it%20will%20not%20delete%20mine%3F\"><h3>The Malware Shield deletes files. How do I know it will not delete mine?<\/h3><\/dt>\n<dd><p>Three gates must all pass before anything is removed:<\/p>\n\n<ol>\n<li>The file must contain an exact marker unique to the malware family this\nscanner targets. The generic \"looks obfuscated\" heuristic can only report,\nnever remove, because licence loaders and minified libraries look identical.<\/li>\n<li>The file must sit in a location this family actually drops into, and must not\nbe part of a real plugin, theme or WordPress core. An infected <em>real<\/em> file is\nreported for reinstalling, never erased. wp-config.php is never deleted.<\/li>\n<li>This plugin's own folder, and other security and backup plugins, are skipped.<\/li>\n<\/ol>\n\n<p>Everything removed is copied into the plugin's own database table first --\nnever onto disk -- and can be downloaded again from the Malware Shield tab.<\/p><\/dd>\n<dt id=\"does%20the%20firewall%20change%20my%20form%20data%3F\"><h3>Does the firewall change my form data?<\/h3><\/dt>\n<dd><p>No. It never edits, strips or re-encodes a submitted request. It runs in\nlog-only mode by default and blocks only if you switch it to Block mode.<\/p><\/dd>\n<dt id=\"should%20i%20disable%20xml-rpc%3F\"><h3>Should I disable XML-RPC?<\/h3><\/dt>\n<dd><p>Only if nothing depends on it. Leave it enabled if you use Jetpack, the\nWordPress mobile app, or any service that publishes to your site remotely.<\/p><\/dd>\n<dt id=\"does%20the%20plugin%20contact%20any%20external%20service%3F\"><h3>Does the plugin contact any external service?<\/h3><\/dt>\n<dd><p>One, and only WordPress.org's own API. When the Malware Shield inspects a file\nin wp-includes, wp-admin or the web root, it asks WordPress's built-in\nget_core_checksums() for the official checksum manifest of your WordPress\nversion, so it can tell a file WordPress genuinely ships from one an attacker\nplanted. That request goes to api.wordpress.org -- the same endpoint WordPress\nitself uses for updates -- and carries only your WordPress version number and\nlocale. Nothing about your site, its content or its users is sent. The manifest\nis cached, and if the request fails the scanner simply reports those files\ninstead of judging them.<\/p>\n\n<p>There is no telemetry, no analytics, no third-party service and no registration.\nEverything else the plugin records stays in your own database.<\/p><\/dd>\n<dt id=\"what%20happens%20when%20i%20delete%20the%20plugin%3F\"><h3>What happens when I delete the plugin?<\/h3><\/dt>\n<dd><p>Uninstall removes its options, its log and quarantine tables and its scheduled\ntasks, on every site in a multisite network. Accounts you demoted\nthrough the account scanner are deliberately left as they are -- silently\nrestoring an administrator during an uninstall would be dangerous.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>2.3.3<\/h4>\n\n<ul>\n<li>Repackaged. No functional changes from 2.3.2.<\/li>\n<\/ul>\n\n<h4>2.3.2<\/h4>\n\n<ul>\n<li>Activating the plugin no longer scans, removes anything or contacts any\nserver. It creates its tables, writes the default settings and schedules its\ncleanup event, and nothing else. Earlier builds ran a cleanup during\nactivation, which could remove files and options before the site owner had\nagreed to anything.<\/li>\n<li>Auto-clean is now OFF by default. Scans report what they find; removal is\nsomething you switch on, or do once with \"Scan &amp; clean\".<\/li>\n<li>A database option is never removed because its name matches. Its stored value\nmust carry an exact malware marker first, so an option belonging to another\nplugin that happens to share a name is reported and left alone.<\/li>\n<li>The WordPress root and content directory are now read in one place each, and\nthe plugins folder is derived from this plugin's own location.<\/li>\n<\/ul>\n\n<h4>2.3.1<\/h4>\n\n<ul>\n<li>The plugins and mu-plugins directories are now read from WP_PLUGIN_DIR and\nWPMU_PLUGIN_DIR only. The old fallbacks assumed those folders sit inside\nwp-content, which is not true on every install.<\/li>\n<li>Fixed: reported paths were labelled \"wp-content\/...\" even on sites where the\ncontent directory has been renamed. The real folder name is used now.<\/li>\n<\/ul>\n\n<h4>2.3.0<\/h4>\n\n<ul>\n<li>Renamed to Stillward Security.<\/li>\n<li>Quarantined files are now kept in the plugin's own database table instead of a\nfolder under \/uploads, so no copy of removed malware is ever stored as a file\non the server. Copies left on disk by earlier builds are moved into the\ndatabase and deleted on update.<\/li>\n<li>A removed file is no longer written back into place. Its copy is offered as a\ndownload instead, so putting a file back into a core, mu-plugins or theme\nfolder is a deliberate step taken by the site owner. Options, cron events and\ndemoted accounts still restore in one click.<\/li>\n<li>An infected theme functions.php is now reported instead of being edited.<\/li>\n<li>Files larger than 1 MB are never removed, because a verified copy of them\ncannot be kept.<\/li>\n<li>Fixed: the WordPress file editor was disabled even with WordPress Hardening\nswitched off. It now follows that setting, and is disabled by withholding the\neditor capabilities instead of defining the DISALLOW_FILE_EDIT constant.<\/li>\n<\/ul>\n\n<h4>2.2.0<\/h4>\n\n<ul>\n<li><strong>The scanner now looks at the database, not only at files.<\/strong> A four-month\nSEO-cloaking campaign across eight sites on one hosting account was missed\nentirely by hourly file scans, for a structural reason: it never wrote a file\nworth finding. Its code lived in a plugin's database table, its configuration\nin an option, its spam in wp_posts, and its administrators were INSERTed by\nSQL. This release adds the deterministic half of the answer \u2014 checks that look\nfor structural facts WordPress itself cannot produce, so there is nothing to\ntune and nothing to guess at. Nothing found in the database is ever removed\nautomatically.<\/li>\n<li><strong>Hidden configuration in wp_options.<\/strong> The payload config was stored in an\nautoloaded option whose <em>name<\/em> was md5(sha1()) of the site's own hostname \u2014 so\nit differed on every site and no blocklist could ever list it. The plugin now\ncomputes the same name from your hostname and simply asks whether the row\nexists, which has no false-positive surface at all. Options named\nwp_custom_range \/ wp_custom_filters are flagged the same way, and any other\n32-character hex option loaded on every request is raised as a warning.<\/li>\n<li><strong>Administrators WordPress did not create.<\/strong> WP_User::add_role() assigns a\nboolean, so WordPress only ever writes <code>s:13:\"administrator\";b:1<\/code>. Every\naccount created by SQL injection on those eight sites wrote a <em>string<\/em> there\ninstead, because the row was built by hand. That one difference found four\nrogue administrators and zero false positives. Duplicated user_login values \u2014\nwhich WordPress refuses to create \u2014 are treated the same way. Softer signals\n(no e-mail address, a registration date that contradicts the account's own ID,\nnever used at all) are reported together as a warning rather than separately\nas noise.<\/li>\n<li><strong>Demote &amp; lock.<\/strong> A confirmed rogue administrator can be demoted to\nSubscriber with its sessions ended and its password reset, in one click and\nwithout deleting anything. The previous role, capabilities and password hash\ngo into the quarantine first, so Restore puts the account back exactly as it\nwas. The plugin refuses to demote the account you are signed in as, or the\nlast administrator on the site.<\/li>\n<li><strong>Content owned by nobody.<\/strong> 4,565 spam posts carried author IDs with no row\nin wp_users. Posts whose post_author matches no user are now reported with the\ncount and the ID (post_author 0 is left alone \u2014 menus legitimately use it), as\nare batches of posts sharing one post_date down to the second. A real import\nlooks identical, so both are warnings with a one-click \"It was me\" that\nsilences that exact fact for good.<\/li>\n<li><strong>The activity log learned what the rest of a compromise looks like.<\/strong> It\nrecorded exactly three kinds of event before: blocked logins, hidden-login\nhits and the file scanner. Nothing about users, options, database code,\ncontent volume or web-root changes \u2014 so most of that incident had no category\nit could have been written under, even in principle. There are eighteen event\ntypes now, and the Activity Log tab lists them all so a missing one reads as a\nblind spot rather than a quiet site.<\/li>\n<li><strong>Critical findings e-mail you immediately.<\/strong> Nobody logs into a brochure\nsite's admin for weeks, which is exactly the window this campaign operated in.\nOne message per distinct finding per day (an alert that arrives hourly and is\nalways the same thing teaches people to delete it unread), at most twelve an\nhour, and only for findings that mean something got in \u2014 routine lockouts and\nfirewall blocks are never mailed.<\/li>\n<li>Administrator creation, promotion to a privileged role, and deletion of a\nprivileged account are each their own logged, notifiable event. Ordinary\nsubscriber registrations are deliberately not logged: on a shop, they would\nbury the one that matters.<\/li>\n<li>Build fix: the release ZIP no longer contains the developer's local <code>.claude\/<\/code>\nconfiguration. Dot-files and dot-directories are now excluded from the build.<\/li>\n<\/ul>\n\n<h4>2.1.4<\/h4>\n\n<ul>\n<li><strong>New Account Scan tab: every WordPress install under the same hosting user,\nin one place.<\/strong> This infection spreads across all sites on a hosting account\nand the infected ones write it back into the sites you have already cleaned,\nso cleaning one at a time never finishes \u2014 and a plugin that can only see its\nown site can never tell you that. The scan is read-only: it never deletes,\nnever touches a database, and never reads database credentials out of another\nsite's wp-config.php. Results can be downloaded as a text report.<\/li>\n<li>Access is by administrator capability and nonce \u2014 WordPress's own model.\nThere is deliberately no separate password: anyone who can reach that screen\nis already an administrator and could read a stored password out of the\noptions table anyway.<\/li>\n<li><strong>Fixed: the scanner reported other security plugins \u2014 including older builds\nof this one \u2014 as infected.<\/strong> Every scanner ships the strings it hunts for, so\nscanning one with another finds \"malware\" in it. On a real hosting account\nthat lit up seven perfectly clean sites. Known signature-bearing plugin\nfolders are now skipped, and Stillward's own source is recognised by content\nas well, which also covers renamed folders and failed-upload leftovers.<\/li>\n<\/ul>\n\n<h4>2.1.3<\/h4>\n\n<ul>\n<li><strong>Keeps working where the host disables WP-Cron.<\/strong> Several hosts set\nDISABLE_WP_CRON and expect a real system cron; where one was never configured,\nthe hourly sweep silently never ran. The Malware Shield tab now warns when\nscheduled events are overdue and gives the exact cron command to add, and the\ncheap per-request clean is run from the admin (throttled to hourly) so the site\nis not defenceless in the meantime.<\/li>\n<li><strong>The admin-account check no longer only matches adm_ names.<\/strong> Real\nattacks also create ordinary-looking administrators with an outside email\naddress, which sailed straight past. An account is now flagged for review when\nit matches this family's naming pattern, or when two softer signals line up \u2014\nan email that is not on the site's domain, an unusually long machine-looking\nusername, or appearing recently on a site that is much older. Still never\ndeleted automatically.<\/li>\n<li>Recency only counts on an established site, so installing the plugin on a\nbrand-new site no longer flags the owner's own administrator account.<\/li>\n<li>The account-wide scanner now finds the hosting account root on its own by\nwalking up from wherever it was uploaded. On cPanel\/hPanel only public_html is\nreachable over the web, so the script has to sit inside one site while scanning\nfrom several levels above it \u2014 previously that meant looking up your own\nusername first.<\/li>\n<li>The System Report now includes ABSPATH and the likely account root, which is\nwhat the account-wide scanner needs.<\/li>\n<\/ul>\n\n<h4>2.1.2<\/h4>\n\n<ul>\n<li><strong>The deep scan is now resumable.<\/strong> A real site with WooCommerce and a page\nbuilder holds far more PHP files than one pass can read on shared hosting, and\nthe old fixed cap meant the same first slice was re-scanned forever while the\nrest of the site was never looked at at all. Each pass now continues where the\nlast one stopped and wraps around at the end, so a few hourly passes cover\neverything. The fixed paths this malware always uses (drop-ins, mu-plugins,\ntheme functions.php, wp-config.php, the cache copy) are still checked in full\non every single pass and are never subject to the cap.<\/li>\n<li>The coverage notice now names the exact range of files a pass covered and where\nthe next one resumes, instead of only saying that it stopped.<\/li>\n<li>Files per pass is configurable, default raised from 6,000 to 20,000, with a\n25-second walking budget so a slow filesystem pauses on time rather than\nrunning the request out.<\/li>\n<li>New <strong>System Report<\/strong> on the Malware Shield tab: one copy-pasteable block with\nPHP\/MySQL\/WordPress versions, OPcache and open_basedir state, cron health\n(including an OVERDUE warning when WP-Cron is not firing), scan progress,\nfindings, settings, drop-ins present and the active plugin list. It contains no\npasswords, keys or database credentials.<\/li>\n<li>Fixed the release ZIP. It had been built with a tool that writes Windows\nbackslashes as path separators, which the ZIP format forbids. PHP's ZipArchive\nthen treated the whole path as a single flat filename, no plugin folder was\ncreated, and WordPress reported \"Plugin file does not exist.\" on upload.<\/li>\n<\/ul>\n\n<h4>2.1.1<\/h4>\n\n<ul>\n<li><strong>Malware Shield now clears the compiled-code cache after cleaning.<\/strong> This was\nthe reason the infection kept coming back: it stays resident in PHP's OPcache\nacross worker processes, so deleting the files changed nothing while a live\nworker still held the compiled copy and simply rewrote them. Each removed file\nis now invalidated individually and the cache is reset at the end of the\nrequest. A full PHP-FPM restart is still required, and the Malware Shield tab\nnow says so in a recovery checklist.<\/li>\n<li><strong>Planted \"core-looking\" files are now removed, infected real ones are not.<\/strong>\nThe family drops files such as feed-atom-framework.php and upgrade-plain.php\ninto wp-includes\/wp-admin to blend in. The shield checks the official\nWordPress checksum manifest: a marker-carrying file WordPress does not ship is\nremoved, while a genuine core file that was injected is only reported, because\nit needs reinstalling rather than deleting. Works for new filenames too, not\njust the known ones.<\/li>\n<li>Scan coverage extended to the places this family also uses: the web root\n(config-main.php), wp-config.php injection, and the wp-content\/cache staging\ncopy. wp-config.php and other load-critical files are never deleted, only\nreported.<\/li>\n<li>Known dropped filenames (in themes and plugins as well) are removed when they\ncarry a marker \u2014 two independent indicators rather than one.<\/li>\n<li>Added one more of this family's markers (the SC_TH \"L\" variant). Reports, without deleting, any other sc_* option and\nany random-looking scheduled event that has no callback function \u2014 the latter\nis what throws \"call_user_func_array(): function ... not found\" on shutdown.<\/li>\n<li>Activating the plugin on an already-infected site now cleans the known paths\nimmediately instead of waiting for the hourly scan, with the deep sweep\nfollowing five minutes later so activation cannot time out.<\/li>\n<li><strong>Fixed: brute-force protection could be bypassed by forging an IP header.<\/strong>\nX-Forwarded-For was trusted unconditionally, so an attacker could send a new\nfake IP on every request and never hit the lockout \u2014 or forge the site owner's\nIP and lock <em>them<\/em> out. The connection address is now used unless you enable\nthe new \"Site is behind a proxy \/ CDN\" option, which reads proxy-written\nheaders instead. The settings screen shows the IP the plugin currently sees so\nyou can check which setting is right for your host.<\/li>\n<li><strong>Fixed: enabling Hide Login locked administrators out of sub-directory\ninstalls.<\/strong> The secret slug was compared against a path that still contained\nthe sub-directory prefix, so the login page 404'd while wp-login.php stayed\nblocked. The site path is now stripped, and wp-admin is detected correctly when\nWordPress lives in its own directory.<\/li>\n<li>Fixed: a lockout renewed itself on every blocked attempt, so sustained\nhammering kept the real owner locked out indefinitely. Attempts made during a\nlockout no longer extend it.<\/li>\n<li>Hide Login now refuses to turn on if the slug collides with an existing page or\npost, and no longer 404s admin-ajax.php \/ admin-post.php for logged-out\nvisitors (which broke public contact forms).<\/li>\n<li>The Strict MIME Whitelist is now editable in the UI. It previously locked\nuploads to five hard-coded types with no way to change them.<\/li>\n<li>Fixed: files such as \"example.com.jpg\" were rejected as double-extension\nattacks. Only web-executable extensions are now matched inside a filename.<\/li>\n<li>Fixed: attack payloads were HTML-escaped twice, so the activity log showed\n\"&lt;script&gt;\" instead of the request.<\/li>\n<li>The log is now rate-limited per IP and event type, so an attack cannot inflate\nthe log table one row per request. Added an index on the severity column.<\/li>\n<li>Multisite support: network activation now installs on every site, sites created\nlater are set up automatically, and deleting the plugin cleans up the whole\nnetwork instead of only the current site.<\/li>\n<li>Security headers are now also sent in wp-admin and on the login screen. They\nwere previously front-end only, because the hook they used does not fire for\nadmin requests. Content-Security-Policy and Permissions-Policy stay front-end\nonly, so the block editor and media plugins are unaffected.<\/li>\n<li>Translations now load: added the missing load_plugin_textdomain() call, the\nDomain Path header, and a languages\/stillward-security.pot template.<\/li>\n<li><strong>Fixed: the Malware Shield could delete legitimate files.<\/strong> Removal now\nrequires an exact malware marker <em>and<\/em> a known malware drop location. Files in\nwp-content\/plugins, wp-content\/themes, wp-admin and wp-includes are reported\nfor review and never deleted.<\/li>\n<li>The generic obfuscation heuristic (previously \"gzinflate + a dynamic variable\"\n\u2014 which matches plenty of legitimate packed code) is now report-only, needs a\ndecoder AND a real execution sink AND an embedded payload before it reports,\nand only runs in \/uploads, mu-plugins and the drop-ins. It is never applied to\ncore, plugin or theme files: testing on a stock WordPress showed PHPMailer and\ngetID3 tripping generic \"looks packed\" tests, because they legitimately call\nbase64_decode()\/gzuncompress() and are full of \\x escapes. A scan of a clean\ninstall now reports nothing at all.<\/li>\n<li>Known-malicious mu-plugin <em>filenames<\/em> are no longer deleted on the name alone \u2014\nthe file must also contain a marker, otherwise it is only flagged.<\/li>\n<li>New quarantine: every removed file, option and cron event is backed up to a\nprivate, web-inaccessible folder and can be restored with one click.<\/li>\n<li>Infected theme functions.php is now backed up before the injected block is\nstripped, and the rewrite is rejected if it would leave invalid PHP.<\/li>\n<li>Cron cleanup no longer matches generic hashed hook names, and database cleanup\nmatches exact option names instead of the loose <code>sc_%<\/code> prefix.<\/li>\n<li>This plugin and other security\/backup plugins are skipped by the scanner, so\ntheir bundled signatures can no longer trigger detections.<\/li>\n<li>New Malware Shield admin tab: scan-only and scan-and-clean runs, findings with\nthe reason each item was or was not removed, and the quarantine list.<\/li>\n<li>Malware Shield settings (enable, auto-clean, quarantine retention) are now\nexposed in the UI instead of being hidden options.<\/li>\n<\/ul>\n\n<h4>2.0.0<\/h4>\n\n<ul>\n<li>Rebuilt to be safe-by-default and portable across any site.<\/li>\n<li>Removed the aggressive SQLi word-matching that could block legitimate form and\ncomment submissions.<\/li>\n<li>Firewall is now read-only, log-only by default, and opt-in.<\/li>\n<li>Removed per-request \"headers sent\" logging (no more DB bloat).<\/li>\n<li>Removed remote wp-login.php download\/overwrite from the hidden-login feature.<\/li>\n<li>Upload protection no longer whitelists MIME types by default (normal files keep\nworking); it blocks only dangerous executable types.<\/li>\n<li>No longer strips ?ver= from asset URLs (keeps cache-busting intact).<\/li>\n<li>New settings UI with safe\/opt-in\/advanced badges and an activity log.<\/li>\n<\/ul>","raw_excerpt":"Lightweight, safe-by-default WordPress security. Hardens your site without interfering with normal behaviour. Aggressive features are opt-in.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/364454","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=364454"}],"author":[{"embeddable":true,"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/adnanali32038"}],"wp:attachment":[{"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=364454"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=364454"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=364454"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=364454"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=364454"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/ur.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=364454"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}