=== DevDome Malware Scanner – Virus Scanner & Malware Removal ===
Contributors: devdome
Tags: malware scanner, malware, malware removal, backdoor, security
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 1.3.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

WordPress malware and virus scanner for malware removal. Detect backdoors, malicious code and infected files, then quarantine and clean threats.

== Description ==

= WordPress Malware Scanner & Malware Removal =

DevDome Malware Scanner is a WordPress malware scanner for detecting malware, backdoors, malicious code, infected files and reinfection risks. Scan WordPress files and database content, quarantine threats, and safely repair trusted files from the dashboard.

It combines malware signatures, file integrity checks, code analysis and WordPress-specific security checks to find modified core files, suspicious PHP, database injections, hidden backdoors, rogue administrators, malicious cron jobs and other persistence mechanisms.

It does not only find infected files. It also looks for the things that bring the malware back after a cleanup, and it keeps uncertain findings separate from confirmed threats.

= What It Finds =

* Malware and malicious code
* Backdoors and web shells
* Modified WordPress core files
* Modified plugin and theme files
* Suspicious PHP files in uploads
* Database injections and hidden spam
* Rogue administrator accounts
* Malicious WP-Cron tasks
* Must-use plugins and drop-ins
* Configuration file changes (wp-config.php, .htaccess, .user.ini)
* Files that may reinfect the site after cleanup

= Scan Files and Database =

The malware scan covers WordPress core, every plugin and theme, the uploads folder, must-use plugins, drop-ins and the configuration files. The database scan looks for script and iframe injections, encoded payloads, SEO spam and hidden links across options, posts, revisions and meta. Administrator accounts are read from the capability values in usermeta, not just role labels, and every WP-Cron event is checked for tasks that can re-download a payload.

Every finding says what changed, why it is suspicious, how confident the scanner is, and what to do about it. Every row expands to a detail panel with the facts behind the decision: file size, modified time, plugin and hash for a file; username, email, role and registered date for an account; hook and next run for a scheduled task; the post or option for database content. For a modified core or plugin file, View what changed shows the lines that differ from the official copy.

= Find Backdoors and Reinfection Risks =

Removing infected files alone does not clean a site that keeps a rogue admin, a cron beacon or a planted must-use plugin behind. These are tracked on their own Reinfection risks tab with a Neutralize action: an unknown administrator loses its admin rights (the account stays and can be restored under Users), a planted must-use plugin or drop-in moves to quarantine, a suspicious scheduled task is removed. The tab also lists every must-use plugin and drop-in present on the site. A neutralized threat that comes back is reported again as reappeared.

= Safe Malware Cleanup =

One click fix closes every back door the plugin can fix safely and reversibly: known malware, executables hidden in uploads, unexpected files in core and high-confidence malicious code are moved to quarantine; modified core and WordPress.org plugin files are repaired by reinstalling the whole package from WordPress.org, with every file that reinstall would change copied to quarantine first. The scan then runs again so the clean verdict is verified, not assumed.

A quarantined file is removed from disk and its contents are kept as a compressed, non-executable copy in the plugin's own database table, together with the original path, SHA-256, size and permissions. Nothing executable is ever written anywhere on disk. It can be restored at any time from the Quarantine tab, and the restored file is verified against the recorded hash. Deleting a quarantined file permanently is a separate, explicit action.

Threats that need a human stay under Needs your decision with a plain explanation and a link to the right place: theme files (replacing them changes your design), injected database content, new administrator accounts, suspicious cron tasks, configuration files, software with a known security hole. Files WordPress cannot boot without are never moved by the plugin.

= File Integrity Checking =

WordPress core files are verified against the official checksums from WordPress.org, including unknown files inside wp-admin and wp-includes. WordPress.org plugins are checked against their official checksums, and WordPress.org themes against the official zip of the installed version: modified, missing and foreign files. Premium and custom plugins and themes are checked against a baseline, with a file change timeline from the second scan on.

A modified core or WordPress.org plugin file is repaired by WordPress itself: the WordPress updater reinstalls the same installed version of the whole package (all of WordPress core, or the whole plugin) from WordPress.org, and the result is verified against the official checksum. Because a reinstall touches more than one file, the flagged file is moved to quarantine and every other file the reinstall would overwrite or delete (a file that differs from the official copy, an extra file in the plugin folder) is copied to quarantine first; each can be restored over the official copy from the Quarantine tab. The plugin never writes into core, plugin or theme folders and never edits files surgically; a failed reinstall puts the original back.

= Severity and Confidence Are Separate =

Severity is how bad a finding would be (Critical, High, Medium, Low, Info). Confidence is how likely it is malicious. A known-signature match has 100% confidence; a high-entropy blob on its own is only a contributing signal and is never called malware by itself. Low and Info rows are notes, not threats, and never trigger an alert. The Overview shows a protection ring that reflects the worst open finding, the verdict in plain words and how many threats can be fixed with one click. Areas the scan could not fully verify (excluded paths, oversized files, an unreachable checksum source) are listed as coverage gaps and never assumed clean.

= Scheduled Malware Scans =

Daily or weekly scans run around 02:00 site time. Scans run in short time-boxed batches (8 seconds by default, adjustable) driven by the open admin page, with a WP-Cron fallback that keeps a scan going after you close the tab. Scans can be paused, resumed and cancelled. One engine failing does not destroy the scan: the failed stage is recorded as a coverage gap and the scan moves on.

= Works Without a DevDome Account =

The local scan is complete without an account: integrity checks, code analysis, uploads, configuration, users, cron, database and persistence engines all run on your server. Nothing is sent to DevDome before you connect. A site that never connected runs on its built-in heuristics and says so in the coverage notes.

= Optional DevDome Account =

Connecting a free DevDome account adds:

* The known-malware signature set: 90,000+ signatures from the Linux Malware Detect project, curated and served by DevDome. Byte-pattern signatures are stored on this site; the hash signatures stay on DevDome's servers, and the SHA-256 and MD5 hashes of every scanned file are checked against them during each scan. Only hashes are sent, never file contents.
* Security status for all your sites in one DevDome dashboard, and an email to your account address when a scan finds critical or high threats, at most once per scan. Clean scans and low-severity findings send nothing.
* Enhanced analysis (separate opt-in): short suspicious code fragments and a file-path hint are sent for a second opinion. The plugin attempts to redact common credential and secret patterns before transmission; no redactor can guarantee that every secret format is removed.

Not connected: the file hash check and signature updates stop. Byte-pattern signatures already downloaded to this site keep matching after a disconnect.

= Advanced Detection Details =

* PHP code is analysed with token-based static analysis: behavior chains such as request input to execution, decode to execution and remote download to disk write, not keyword matching.
* Obfuscation: encoded blobs, entropy, chr() chains, hex escapes, nested decoders.
* Uploads: executable files, disguised media (photo.jpg containing PHP), extension and content mismatches.
* Configuration files: auto-prepend directives, cloaking redirects, PHP handlers in uploads.
* Known-malware signatures: byte patterns matched on this site and file hashes checked through your DevDome account.
* Optionally, installed plugin, theme and WordPress versions against the WPVulnerability database, plus plugins closed on WordPress.org for a security issue.
* Every finding carries human-readable evidence: the signals that fired, the line, a short snippet with secrets removed. A file is reported as quarantined only after its database record is written; if the record fails, the file is put back.

= Simple and Advanced Views =

The Simple view shows the verdict, the one-click cleanup and Settings. The Advanced view adds Threats with severity filters, search and bulk actions (Quarantine, Replace with official file, Trust this exact content, Ignore, False positive, Reopen), File Changes (last 90 days), Reinfection risks (Neutralize), Quarantine (Restore, Delete permanently, Remove from list) and Scan History.

= Multisite =

On a network the scanner is a network administrator tool, because integrity checks, quarantine and repair touch shared core and plugin files. Each site keeps its own scan data; network deactivation clears scheduled scans on every site.

= AI and Agent Support =

On WordPress 6.9 and newer, DevDome Malware Scanner registers WordPress Abilities covering the whole plugin: the security verdict with the fix plan, findings with every filter, finding details with evidence, the diff against the official copy, scan start, pause, resume, cancel, progress and history, One click fix, every per-finding action (quarantine, repair, trust, false positive, ignore, acknowledge, reopen, neutralize a rogue administrator, cron task or planted file), restore, delete or forget quarantined copies, the file change and event logs, settings (read and update), the simple or advanced view and the signature download. Compatible AI agents and MCP clients can discover and use these abilities when the site exposes them, for example through the official WordPress MCP Adapter. Every ability runs the same code as the plugin screens under the same administrator capability; a finding action is refused unless it is one the screen offers for that finding, and One click fix, quarantine, repair, neutralize, restore and permanent deletion require an explicit confirm flag from the agent.

== External services ==

The plugin talks to the following services:

1. WordPress.org APIs (api.wordpress.org, downloads.wordpress.org, core.svn.wordpress.org, plugins.svn.wordpress.org): used to fetch official core and plugin checksums, plugin directory status (whether an installed plugin was closed for a security issue), the official zip of each installed WordPress.org theme (downloaded during a scan to verify the theme's files) and, on explicit repair or comparison actions, official file copies. Only your WordPress version, locale, plugin and theme slugs and their versions are sent. Terms: https://wordpress.org/about/privacy/ Privacy: https://wordpress.org/about/privacy/

2. DevDome Security API (analytics.devdome.com), only on a site connected to a DevDome account: (a) during each scan, the SHA-256 and MD5 hashes of every scanned file are checked against the DevDome database of 90,000+ known-malware signatures (hashes only, never file contents); (b) after every scan a summary (severity counts, the type, title, path and status of the top findings, the site's wp-admin URL for the email button, the signature-set version and the plugin version) is sent so your DevDome dashboard shows the site's security status and your account address gets an email when critical or high threats are found; (c) the byte-pattern signature set is delivered and updated through the account and stored on this site; the hash signatures stay on DevDome's servers and are only ever queried by hash; (d) with the separate "Enhanced analysis" opt-in, short suspicious code fragments and a file-path hint are sent for a second opinion; the plugin attempts to redact common credential and secret patterns before transmission, but no redactor can guarantee that every secret format is removed. Authentication is the site token the DevDome Dashboard provisioned on connect. Nothing is sent before you connect. Terms: https://devdome.com/terms-of-service Privacy: https://devdome.com/privacy-policy

3. WPVulnerability API (www.wpvulnerability.net), only when you switch on the vulnerability check in Settings (off by default): during a scan, the slugs and versions of your installed plugins, themes and WordPress core are checked against this free public vulnerability database (EUPL v1.2). Only slugs and version numbers are sent. Terms: https://www.robotstxt.es/legal/ Privacy: https://www.wpvulnerability.com/privacy/

Signature data source (not a service the plugin contacts): the signature set is the Linux Malware Detect database (GPLv2, rfxn.com project), imported and served by DevDome; the plugin never contacts rfxn.com. Project page: https://www.rfxn.com/projects/linux-malware-detect/ License (GPLv2): https://www.gnu.org/licenses/old-licenses/gpl-2.0.html

4. Plugin catalog (devdome.com): the DevDome Dashboard inside wp-admin fetches the list of DevDome plugins (names, descriptions, logos, links, WordPress.org slugs) from `https://devdome.com/wp-plugins/catalog.json` at most once every 12 hours, and only after you have connected the site to a DevDome account; a site that is not connected never contacts devdome.com for it. Only the bundled core version is sent in the request; no site or visitor data. Terms: https://devdome.com/terms-of-service Privacy: https://devdome.com/privacy-policy

5. Connecting a DevDome account (optional): the DevDome Dashboard offers connecting a free DevDome account. Nothing is sent until you press the Connect button. If you do connect, the shared library sends your site address, a generated site ID and a generated secret site token to `analytics.devdome.com/api/plugin/connect/start` and `/api/plugin/connect/claim` to link this site to your account; afterwards it confirms the connection with `api.devdome.com/plugin/account` at most once every fifteen minutes while you use the dashboard (once an hour after a refusal), and tells `api.devdome.com/plugin/disconnect` when you disconnect. Terms: https://devdome.com/terms-of-service Privacy: https://devdome.com/privacy-policy

6. Error reports (devdome.com), only when you press "Report this error" on an error message: the plugin sends the error text, the plugin, WordPress and PHP versions, the screen you were on, the last 20 scanner log lines with secrets removed, your site address and your admin e-mail (so support can reply) to `https://devdome.com/api/plugin/error-report`. Nothing is sent unless you press the button. Terms: https://devdome.com/terms-of-service Privacy: https://devdome.com/privacy-policy

The scanner does not require a DevDome account. WordPress.org verification services may still be contacted as described above.

== Build ==

The admin stylesheet `assets/devdome-tools-tw.css` is compiled with Tailwind CSS 3.4.19 from the shipped source `src/tw.css`. No package.json or build tooling is included in the distribution (WordPress.org review, September 2026). To reproduce the file with the standalone Tailwind CLI (https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.4.19), run from the plugin folder:

`tailwindcss -i ./src/tw.css -o ./assets/devdome-tools-tw.css --minify --content "./admin/**/*.php,./assets/admin.js"`

The content globs above are the plugin's complete Tailwind configuration (no theme extensions, no plugins). `assets/admin.js` is plain, unminified JavaScript and is edited directly; there is no build step for it. The two inline header icons (bug report, save) are from Lucide (ISC licence).

== Installation ==

1. Upload the plugin and activate it.
2. Open Malware Scanner in the admin menu and press Scan Now.
3. The first scan verifies integrity and seeds the file baseline; later scans add the change timeline.
4. Uninstalling removes the plugin's tables, settings and scheduled tasks, including the quarantine table. Restore or delete quarantined files before uninstalling if you still need them.

== Screenshots ==

1. Malware Scan Overview: threat status, severity counts and safe cleanup actions.
2. Malware and Backdoor Findings: review suspicious files, malicious code and the evidence behind each finding.
3. File Changes: track created, modified, quarantined and repaired WordPress files over time.
4. Reinfection Risks: find rogue admins, malicious cron jobs, must-use plugins and drop-ins.
5. Malware Quarantine: safely quarantine suspicious files and restore them when needed.
6. Scan History: review previous malware scans and detected threats.
7. Settings: optional DevDome account features, vulnerability check, scan limits and excluded paths.

== Frequently Asked Questions ==

= Does the plugin delete files? =
No. Threats are moved to quarantine (a compressed, non-executable copy in the plugin's database table; the file itself is removed from disk) and can be restored from the Quarantine tab at any time. The only deletion is the explicit Delete permanently button on a quarantined file. Uninstalling the plugin removes its tables, settings and scheduled tasks, including the quarantine table, so restore or delete quarantined files first if you still need them.

= Do I need a DevDome account? =
No. Every scanning engine runs on your own server: core, plugin and theme integrity, code analysis, uploads, configuration files, users, cron, database and persistence. Connecting a free account adds the 90,000+ known-malware signatures, the security dashboard and the alert email. Without an account the signature set is never downloaded, so there is no known-malware signature matching; the scanner relies on its heuristics and reports that in the coverage notes. If you connect and later disconnect, the byte-pattern signatures already on this site keep matching, but updates and the file hash check stop.

= Can a cleanup break my site? =
It is built not to. Files WordPress cannot boot without are never quarantined. A repair reinstalls the whole package (all of WordPress core, or the whole plugin) through the WordPress updater, verified against the official checksum afterwards; the flagged file and every other file the reinstall would change are copied to quarantine first, and if the reinstall fails the original is put back. Theme files, database content, user accounts, cron tasks and configuration files are never changed automatically, with one exception: a theme file that matches a known-malware signature is quarantined (restorable from the Quarantine tab). Anything quarantined can be restored with one click.

= Why does the scan list coverage gaps? =
Because unscanned is not the same as clean. Excluded paths, files above the deep scan size limit, an unreachable checksum source, a file the previous scan could not finish and a stage that failed are all listed on the Overview, and findings on files that could not be rechecked are kept, not resolved.

= Does a scan slow down my site? =
Scans run in short batches (8 seconds per request by default, 3 to 25 in Settings) while the admin page is open, and continue through WP-Cron when it is closed. You can pause or cancel at any time. The scan does use PHP time on your server, so lower the batch budget on very constrained shared hosting.

= What is the difference between severity and confidence? =
Severity is the potential impact (Critical, High, Medium, Low, Info). Confidence is how likely the finding is malicious. A known-signature match has 100% confidence; a high-entropy blob on its own is only a contributing signal and never called malware by itself. Low and Info rows are notes, not threats, and never trigger an alert.

== Changelog ==

= 1.3.0 =
* Fewer false alarms, seen on real sites: a plugin's own empty "Silence is golden" index.php inside its storage or cache folder is no longer reported as an unknown PHP file (it came back after every quarantine); code patterns inside a Composer library a plugin ships (listed in its vendor/composer/installed.json) are LOW notes instead of MEDIUM alarms; the options of head/footer-code plugins (Head, Footer and Post Injections, Insert Headers and Footers, WPCode and similar) are LOW "review what you put there" notes, not HIGH injection alarms.
* Repair: a core file repair refuses while wp-includes/version.php itself has an open finding, so the WordPress version that chooses the package can never come from an attacker; repairs run one at a time (also across the sites of a multisite network).
* Shared core 1.7.0: Disconnect proves the connection was cleared on this site and says so when it was not; a site token removed elsewhere reads as disconnected at once; plugins without health checks show "Not monitored" on the DevDome dashboard instead of "No Issues"; overlapping bot IP ranges are merged.
* Report this error: every error banner and failed-action dialog has a button that sends the error text, the plugin and WordPress versions and the last scanner log lines (secrets redacted) to DevDome support with one click, no form. Nothing is sent unless you click it.
* Database guard: every failed database query during a scan tick, a screen action, an AI agent ability or the scanner page is now recorded as it happens. A failed read can no longer pass as an empty answer: the plugin refuses to write findings, statuses or baselines after one, actions and abilities answer a database error instead of "done", a scan stage with a failed query is marked failed (nothing auto-resolved) and the scanner page shows a warning when it was built over one.
* Shared core 1.7.0: new read-only ability devdome-tools/get-connection for AI agents (connected, account ID, plan, installed DevDome plugins); a site that moved to another domain resets its connection instead of showing the old domain's; on a subdirectory multisite every site shares the network's connection.
* Shared core 1.7.0 (account connection): a proxy error page or a short outage of the account server no longer clears a live connection (only the server's own "not linked" answer does); Disconnect reports "still listed" unless the server confirmed the unlink; the final connect confirmation requires a real success response; clearer connect error texts.
* Database scan: an oEmbed cache row (_oembed_ postmeta) holding a visible embed from an unlisted provider is now a LOW embed note, not a critical injection. WordPress rebuilds those rows from the post on every view, so the old CRITICAL finding came back after each quarantine. Hidden iframes stay flagged. The finding text now says to remove the embed from the post instead of the row.
* Quarantine: a record whose last database write failed after the file was already removed is finished by Restore or Delete instead of staying stuck, and unfinished records are always listed (screen and agents). Secret redaction also covers heredoc and nowdoc values assigned to bracketed keys, unquoted .env / INI / YAML values, wrapped literals (base64_decode, hex2bin) and concatenated putenv() values. Hidden-frame checks decode numeric entities written without a semicolon. A record still being stored is never restored, and the backups a running repair made stay locked until it finishes. Read-only agent abilities report the account connection from the cached state without re-verifying it.
* Code analysis follows request input through one assignment (a variable that took a superglobal counts like the superglobal in include, exec and callback checks); short-echo tags followed by a quote, digit or parenthesis are recognised in uploads; an official plugin holding a file the package does not ship (a stray .htaccess, .js or .svg) is no longer skipped by the file scan. Demoted or unscheduled findings read as handled; a demote retried after a lost status write closes the finding; the Overview says when the reinfection risks could not be read; dashboard tiles use the last completed scan, not a failed later attempt; agents get a database error, not "not found", when a finding read fails.
* A restore interrupted after the file was written finishes reopening its finding on the next try. Backups made by a running repair stay locked per repair (two repairs at once never unlock each other), through the rollback. Code analysis follows request input through appended strings and through functions that return it, and judges the callback argument of array_filter, usort, array_walk, array_reduce and preg_replace_callback. Secret redaction also covers commented or parenthesised values, nested wrappers, YAML block values, hyphenated JSON keys, XML elements, HTTP Basic headers, passwords inside connection URLs and sprintf-built putenv() calls. Read-only agent abilities no longer trigger a signature or catalog download; scan history is not pruned when the keep-newest lookup fails.
* The only network super admin is never demoted; the file-changes list shows the whole 90-day window; the deep-scan size setting has an upper bound (64 MB); bulk Ignore, Mark as false positive and Remove from list ask first; an unlistable must-use plugins folder reads "unavailable"; agents get an error, not "not running", when the scan table cannot be read; a cloud switch can always be turned off without an account; signature downloads stop retrying after repeated API failures and keep the feed's own severity levels; a missing core file fails core verification; a plugin folder that is a symbolic link is never treated as verified; an empty checksum answer from WordPress.org is retried within the hour instead of being remembered for a week; short-echo PHP tags before a minus sign and PHP code inside .svg or .html uploads are recognised; $pass, $pwd and XML credential values are redacted.
* An account that still holds administrator-level capabilities through a role is never reported as demoted; a repair backup put back over the official copy reopens its finding even when the earlier attempt was interrupted; a repair refuses to start when it cannot lock its backups; credential values concatenated behind comments, parentheses or wrapper calls are redacted.
* A signature download that finishes while a scan is reading the signature table no longer leaves that scan matching against a mix of the old and the new set, and the download reads its final writes back. A scan whose saved progress record is damaged ends as failed instead of completing with nothing verified. The screens say when the scan history could not be read. Theme vulnerability findings are typed as themes. Agent output keeps file names for paths that contain spaces. A PHP short tag glued to a dangerous call (system, eval, include) counts as PHP code when short tags are on. A script tag hidden in the middle of a large image or SVG upload is found. A failed site list during network activation or deactivation is recorded.
* A findings read that fails after the counts were taken never shows the green pill or verdict; the progress poller and the finding buttons report a database error instead of "no scan" or "not found"; One click fix and the run-scan ability read the scan state back before acting. A hash lookup that answers without results keeps the known-malware findings open. A failed reinstall can always roll back its own backups, even while another repair runs, and two repairs starting at once keep both their locks. An unreadable scheduled-task list fails the cron check instead of clearing it; unreadable configuration files keep their earlier findings; a revision holding PHP is judged like its parent post; a symbolic link to an absolute path outside the site is flagged; PHP hidden in the middle of a large HTML upload is found; accounts with raw administrator capabilities join the administrator roster; a linked official plugin file or theme folder is never read through. Signature download failures are abandoned cleanly and every progress write is read back. A scan finishing in the same instant a new one starts no longer clears the new scan's pointer. A dismissed bulk confirmation re-enables Apply.
* Restoring a repair backup of a file whose threat had since been marked resolved reopens that threat; a repair whose lock could not be taken never touches the other repairs' locks.
* Every "is a scan running" check (Scan button, scheduled start, One click fix, Cancel) reports a database error instead of assuming no scan; a scan that ends never clears the pointer of a scan that started right after it; the progress poller and the background worker report an unreadable scan row; the DevDome dashboard says "unavailable" instead of "never scanned" on a failed read. A file whose current hash could not be read fails the scan stage instead of keeping a false-positive decision; configuration files larger than the read cap are recorded as partly checked; a lost core file list degrades the core check instead of skipping it; a database value used as an include path is a finding; array_filter on request data without a callback is no longer flagged; a linked folder on the way to an official plugin or theme file is never read through. Signature downloads swap the set under a marker the scan matcher honours, and a restarted download reads its state back. A cloud code-analysis answer without results is a coverage note.
* A new administrator, a new must-use plugin or drop-in, or a changed critical setting stays listed until you decide about it; earlier scans closed those findings on the next scan because the item had become part of the stored snapshot. Saving settings while the current settings cannot be read no longer replaces the schedule and the account switches with defaults. The automatic-scan schedule is only reported as applied when the scheduled-task list could be read.
* Every action that acts on a finding or a quarantine record by id (quarantine, repair, compare, restore, delete, remove from list) reports a database error instead of "not found" when the record could not be read, and the stored lists of administrators, must-use plugins and drop-ins are never rebuilt from a failed read.
* Every "is a scan running" check reports a database error when the scan pointer itself could not be read; demoting an administrator and One click fix run one at a time; a scan whose progress could not be saved ends as failed instead of restarting on stale state; no file hash or report is sent to DevDome when the settings could not be read (the defaults are never used for sharing); the repair backup lock fails closed when it cannot be read; a signature set is never matched while its version marker is unreadable; option names containing "secret" or "key" without an underscore are scanned again (an escaping mistake excluded them); an unquoted password with spaces is redacted whole; accounts that can activate plugins count as administrators; a database value used as a callback is flagged; official file lists can never point outside the plugin or theme folder; every site of a network gets its own uploads folder for quarantine; the schema version is recorded only when the tables exist.
* Reopening a trusted finding refuses when the trusted list could not be read instead of leaving the file silenced; trusting a file never rebuilds the trusted list from a failed read; a scan whose saved progress went missing after the first stage ends as failed instead of completing clean.

= 1.2.2 =
* Screens never show a clean or empty list when the database read failed: the Threats, Reinfection risks, File changes, Quarantine and History tabs say "unavailable" instead.
* Counts on the screens and in the agent summary are no longer taken from a capped window (header threat split, severity pills, reinfection risks, quarantine history).
* Agents: the security summary, fix plan and quarantine list refuse with an error when the findings table could not be read; the scan status names an uncounted findings table; turning any cloud switch on (hash lookup, code analysis, report) needs confirm: true; a neutralize whose demotion or unscheduling succeeded but whose status write failed says exactly that; sync-signatures reports the real start result.
* Scanners: an unreadable file in uploads, wp-config.php or a .htaccess / .user.ini is recorded as not checked instead of being scanned as an empty file; an unreadable must-use plugins folder or wp-content no longer wipes the stored snapshot; privileged config files beyond the checked set are named.
* Fix plan: a changed plugin file outside the writable roots is a decision, not a repair that would be refused.
* One-click fix: rows with a backslash in the path no longer freeze the progress loop, and a batch with failed rows is never announced as closed.
* Secret redaction also covers plain token, access_token, refresh_token, secret_key and application password assignments.
* A false-positive decision whose trust undo failed reports that the content is still trusted.

= 1.2.1 =
* Neutralize on a scheduled task now removes every event of the hook, including events scheduled with arguments, and verifies none remain before the finding is marked handled.
* Repair backs up everything the package reinstall may overwrite or delete (files differing from the official copy, extra files in the plugin folder) as copies in quarantine, restorable over the official copy; the scope of a repair is stated in the fix plan, the confirmation and the ability descriptions.
* A failed reinstall now puts every backed-up package file back, and restoring a backup over the reinstalled file keeps the displaced file safe if the write fails.
* Scheduled-task details show the next run for any event of the hook; agent output carries no absolute server paths; settings updates are read back before they report success and changes that lower protection need an explicit confirm.
* Abilities hardening: finding-action only accepts actions the screen offers for that finding (the screens enforce the same list); One click fix, quarantine, repair, neutralize, restore and permanent quarantine deletion require confirm: true; per-ability idempotency annotations; user email no longer included in finding details.
* The official-copy comparison now compares lines in order: a file whose code was only moved around is reported as modified, not as whitespace drift. Package repair refuses a plugin folder that contains a symbolic link (no backup is possible for it). A restore whose recorded permissions cannot be applied is rolled back and reported instead of counted as success.
* A restore whose rollback fails keeps the file it displaced as its own quarantine record (restorable over the path) and says so. Core repair backs up the files inside obsolete folders the reinstall removes. A permanent delete that lost its final database write shows as Deleting and is finished by pressing Delete again. The finding-action ability is annotated destructive (neutralize removes scheduled events for good).
* Evidence snippets are redacted before they are shortened, so a credential on a very long line never reaches stored evidence or agent output.
* Core repair refuses to run when a core file or its folder is a symbolic link (the reinstall would write outside the site with no backup) and also backs up the Genericons example.html files WordPress removes from plugins and themes. A quarantine copy from a pre-1.0.2 store is only touched inside a link-free quarantine folder, and a copy that cannot be removed stays tracked instead of being forgotten.
* Core repair also refuses when a missing core file would be created below a symbolic link, and the Genericons check reads whole files and follows links the way WordPress does, refusing any match it could not back up. Turning cloud code analysis on through the update-settings ability now needs confirm: true (it sends suspicious code fragments to DevDome).
* Core repair also backs up the legacy Requests library files WordPress removes, and its Genericons search walks every folder without a depth limit (looping links end safely). A multi-line private key in a modified file is redacted as a whole in the official-copy comparison. The account Disconnect says so when the DevDome account server could not be told.
* Every file that ships with WordPress core is now repair-only: quarantine is refused for all of them (the official checksum map decides), not just for the handful of bootstrap files. A credential whose value sits on its own line is redacted in the official-copy comparison. Pausing a scan reads the saved state back and reports when the pause did not stick.
* The secret redactor never swallows a line break, so the official-copy comparison keeps its line numbers while redacting; if a line count ever moves, every line that is part of a recognised secret is replaced whole. Resuming a scan reads the saved state back too.
* Secret values that span several lines (a define, an environment value, an assignment, a key block) are redacted whole with their line count kept. One click fix reads every active finding, not the first 500, and both it and the findings list report a failed database read instead of an empty result.
* The theme integrity check extracts the official theme copy through the plugin's own direct file access, so a host whose WordPress file method is FTP no longer ends the scan with a failed theme stage.

= 1.2.0 =
* WordPress Abilities API: 20 abilities covering every feature (verdict and fix plan, findings, evidence and diff, scan control, One click fix, every finding action incl. neutralize, quarantine restore and delete, logs, settings, view mode, signatures). Empty-input abilities refuse unexpected arguments cleanly.

= 1.1.0 =
* WordPress Abilities API support (WordPress 6.9+): seven read and scan abilities for AI agents and MCP clients: get-security-summary, get-findings, get-finding-details, get-scan-progress, run-malware-scan, get-scan-history, get-quarantine. Cleanup actions are not exposed.

= 1.0.3 =
* New plugin icon (shield with magnifier) in the admin header, matching the WordPress.org listing.

= 1.0.2 =
* Quarantine no longer keeps files on disk: the file's contents are stored compressed in the plugin's own database table and the file is removed; restore verifies the hash. Copies made by 1.0.1 are moved into the database on upgrade.
* Local file reads go through the WordPress filesystem layer; temporary files and folders are removed through it too.
* The database scanner never reads options that hold credentials (WordPress Connectors and any API key, token, secret or application password option).
* Build tooling is no longer shipped in the distribution zip.

= 1.0.1 =
* Quarantine store moved to uploads/devdome-malware-scanner/quarantine (resolved through wp_upload_dir(), protected by deny rules and non-executable names).
* Trusted repair now hands the write to the WordPress updater: core files are restored by reinstalling the running WordPress version, plugin files by reinstalling the same plugin version from WordPress.org. The plugin no longer writes into core or plugin folders.
* Suite dashboard: installing a sibling plugin no longer activates it; activation is a separate click.

= 1.0.0 =
* First public release.
* Scans WordPress core, WordPress.org plugins and themes against their official copies, and premium or custom code against a baseline with a file change timeline.
* Token-based PHP analysis for behavior chains (request input to execution, decode to execution, remote download to disk write), obfuscation detection, uploads inspection, configuration files, must-use plugins and drop-ins, administrator accounts, WP-Cron events and database injections.
* Known-malware signatures through an optional DevDome account: byte patterns matched on the site, file hashes checked on the DevDome servers, only hashes ever sent.
* One-click cleanup: quarantine (renamed, non-executable, restorable) and trusted repair through the WordPress updater (reinstall of the same version from WordPress.org), every action bound to the exact file contents the finding was made on.
* Honest verdicts: unscanned, unreadable or partially scanned areas are never assumed clean, a failed or cancelled scan never replaces the last completed result, and coverage gaps are listed.
* Reinfection risks tab: unknown administrators, must-use plugins, drop-ins and scheduled tasks that would let an attacker back in, with a Neutralize action; every finding expands to a detail panel with the facts and links that lead to the fix.
* Time-boxed batches with pause, resume and cancel, a WP-Cron fallback, scheduled daily or weekly scans, Simple and Advanced views, and multisite support.
