{"id":355276,"date":"2026-08-23T12:19:18","date_gmt":"2026-08-23T12:19:18","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/bulwarden\/"},"modified":"2026-10-19T20:08:48","modified_gmt":"2026-10-19T20:08:48","slug":"bulwarden","status":"publish","type":"plugin","link":"https:\/\/mlt.wordpress.org\/plugins\/bulwarden\/","author":23550935,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.40.2","stable_tag":"1.40.2","tested":"7.1.3","requires":"6.0","requires_php":"7.4","requires_plugins":null,"header_name":"Bulwarden","header_author":"Justin Gloeckl","header_description":"Site agent for remote health monitoring, malware scanning, zero-knowledge encrypted backups, and remote core\/plugin\/theme updates, driven by a Bulwarden hub you connect it to.","assets_banners_color":"665fca","last_updated":"2026-10-19 20:08:48","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/bulwarden.net","header_author_uri":"","rating":0,"author_block_rating":0,"active_installs":10,"downloads":1498,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.13.7":{"tag":"1.13.7","author":"bulwarden","date":"2026-08-23 12:18:57","revision":3661809},"1.14.0":{"tag":"1.14.0","author":"bulwarden","date":"2026-08-26 13:12:02","revision":3667070},"1.15.0":{"tag":"1.15.0","author":"bulwarden","date":"2026-08-26 19:29:26","revision":3667716},"1.16.0":{"tag":"1.16.0","author":"bulwarden","date":"2026-08-27 13:52:58","revision":3668981},"1.17.0":{"tag":"1.17.0","author":"bulwarden","date":"2026-08-31 14:04:24","revision":3674412},"1.17.1":{"tag":"1.17.1","author":"bulwarden","date":"2026-08-31 15:28:58","revision":3674563},"1.18.0":{"tag":"1.18.0","author":"bulwarden","date":"2026-08-31 17:01:58","revision":3674665},"1.19.0":{"tag":"1.19.0","author":"bulwarden","date":"2026-09-01 18:49:39","revision":3676675},"1.22.2":{"tag":"1.22.2","author":"bulwarden","date":"2026-09-12 19:10:42","revision":3693045},"1.23.0":{"tag":"1.23.0","author":"bulwarden","date":"2026-09-14 07:41:01","revision":3694704},"1.25.0":{"tag":"1.25.0","author":"bulwarden","date":"2026-09-14 14:08:03","revision":3695357},"1.26.0":{"tag":"1.26.0","author":"bulwarden","date":"2026-09-14 18:41:29","revision":3695780},"1.27.0":{"tag":"1.27.0","author":"bulwarden","date":"2026-09-15 13:14:22","revision":3697027},"1.27.1":{"tag":"1.27.1","author":"bulwarden","date":"2026-09-15 14:11:46","revision":3697107},"1.27.2":{"tag":"1.27.2","author":"bulwarden","date":"2026-09-15 16:18:35","revision":3697350},"1.27.3":{"tag":"1.27.3","author":"bulwarden","date":"2026-09-15 16:46:03","revision":3697402},"1.28.0":{"tag":"1.28.0","author":"bulwarden","date":"2026-09-15 19:57:13","revision":3697644},"1.28.2":{"tag":"1.28.2","author":"bulwarden","date":"2026-09-16 19:25:25","revision":3699174},"1.29.1":{"tag":"1.29.1","author":"bulwarden","date":"2026-09-17 14:06:42","revision":3700416},"1.29.3":{"tag":"1.29.3","author":"bulwarden","date":"2026-09-19 20:30:25","revision":3703649},"1.30.0":{"tag":"1.30.0","author":"bulwarden","date":"2026-09-24 15:12:44","revision":3711572},"1.30.1":{"tag":"1.30.1","author":"bulwarden","date":"2026-09-25 09:19:21","revision":3712748},"1.31.1":{"tag":"1.31.1","author":"bulwarden","date":"2026-09-27 09:54:58","revision":3715265},"1.32.0":{"tag":"1.32.0","author":"bulwarden","date":"2026-09-27 14:23:01","revision":3715608},"1.33.0":{"tag":"1.33.0","author":"bulwarden","date":"2026-09-27 15:18:06","revision":3715656},"1.33.1":{"tag":"1.33.1","author":"bulwarden","date":"2026-09-27 15:32:37","revision":3715667},"1.35.0":{"tag":"1.35.0","author":"bulwarden","date":"2026-09-30 17:18:49","revision":3721741},"1.36.0":{"tag":"1.36.0","author":"bulwarden","date":"2026-09-30 21:08:14","revision":3722038},"1.37.0":{"tag":"1.37.0","author":"bulwarden","date":"2026-10-01 16:58:59","revision":3723522},"1.37.1":{"tag":"1.37.1","author":"bulwarden","date":"2026-10-03 09:59:57","revision":3726073},"1.37.2":{"tag":"1.37.2","author":"bulwarden","date":"2026-10-03 13:54:51","revision":3726332},"1.37.3":{"tag":"1.37.3","author":"bulwarden","date":"2026-10-08 11:59:40","revision":3734474},"1.37.4":{"tag":"1.37.4","author":"bulwarden","date":"2026-10-08 14:52:53","revision":3734889},"1.37.5":{"tag":"1.37.5","author":"bulwarden","date":"2026-10-13 15:01:57","revision":3744265},"1.38.0":{"tag":"1.38.0","author":"bulwarden","date":"2026-10-17 20:15:20","revision":3752330},"1.40.2":{"tag":"1.40.2","author":"bulwarden","date":"2026-10-19 20:08:48","revision":3755484}},"upgrade_notice":{"1.23.0":"<p>The malware scan gains eighteen checks written against real backdoors, and eight\nexisting checks stop reporting honest code. If your provider&#039;s hub had refused an\nupdate as infected, it will go through after this.<\/p>","1.22.2":"<p>Nothing on your site changes. This release exists to correct what the plugin&#039;s\nown listing says about it, which had fallen behind several releases.<\/p>","1.22.1":"<p>Fixes backups of sites larger than 4 GB, which failed while verifying the\nfinished archive. If you have such a site, run a backup after updating.<\/p>","1.22.0":"<p>A restore that cannot finish no longer leaves your site half-restored, and a\nrestore that ran into trouble now says so instead of reporting success. Nothing\nyou had set up changes; no action needed.<\/p>","1.21.0":"<p>The activity log now records the updates your provider installs on this site,\nand three other things it was missing. Nothing else changes; no action needed.<\/p>","1.20.0":"<p>A plugin that appears on your site without WordPress.org publishing it now\nstarts a malware scan by itself, within seconds, instead of waiting for the next\nscheduled one. Nothing is blocked or deleted; no action needed.<\/p>","1.19.0":"<p>Adds German, server-configuration checks, email delivery monitoring and a\ncancel button for a stuck scan, and stops a slow update on shared hosting being\nkilled part-way. Nothing you had set up changes; no action needed.<\/p>","1.18.0":"<p>The malware scan now recognises backdoors that arrive dressed as an ordinary\nplugin, not just obfuscated webshells. Nothing you had set up changes; no action\nneeded.<\/p>","1.17.1":"<p>Fixes false &quot;a WordPress core file is missing&quot; and &quot;no longer matches the\nofficial release&quot; findings on the readme and licence files a non-English\nWordPress installs under a translated name.<\/p>","1.16.0":"<p>The support card on your dashboard can now show your provider&#039;s telephone\nnumber, if they have set one in their hub. Nothing you had set up changes; no\naction needed.<\/p>","1.15.0":"<p>The malware scan now counts the plugins on disk itself instead of taking\nWordPress&#039; word for what is installed, so a plugin that has hidden itself from\nthe plugin list is reported &mdash; and checked against WordPress.org like every\nother one. Must-use plugins are now listed too: they run on every request and\nthe admin gives you no way to switch one off, so the scan reports what is there.\nNothing you had set up changes; no action needed.<\/p>","1.14.0":"<p>Adds a &quot;Request help&quot; button to the dashboard support card, and lets your hub\nfind and clear the files old backups, restores and migrations left behind.\nNothing you had set up changes; no action needed.<\/p>","1.13.7":"<p>Identical to 1.13.5 and 1.13.6 in behaviour. Nothing on your site changes.<\/p>","1.13.6":"<p>Identical to 1.13.5 in behaviour. It exists so the plugin directory&#039;s listing\nand its screenshots describe the same version; nothing on your site changes.<\/p>","1.13.5":"<p>Corrections to the Bulwarden screen rebuilt in 1.13.4. Nothing changes in how\nthe plugin works; no action needed.<\/p>","1.13.4":"<p>The Bulwarden screen has been rebuilt around what you actually came to it for.\nNothing you had set up changes; no action needed.<\/p>","1.13.3":"<p>Bulwarden moves out of the Settings submenu into its own entry in the WordPress\nadmin menu. Nothing else changes; no action needed.<\/p>","1.13.2":"<p>Fixes a false &quot;a WordPress core file no longer matches the official release&quot;\nfinding on <code>wp-includes\/version.php<\/code> &mdash; raised on any site whose display\nlanguage differs from the WordPress package it was installed from.<\/p>","1.13.1":"<p>Fixes a false &quot;a plugin file no longer matches the version published on\nWordPress.org&quot; finding on plugins WordPress.org publishes more than one copy of.<\/p>","1.13.0":"<p>Your hub can now see and finish an update that changed this site&#039;s files but\nnever completed &mdash; a pending database upgrade or a leftover maintenance\nlock. Restores can also put back only the database, leaving files untouched.<\/p>","1.11.0":"<p>Must be installed alongside the matching hub update: the two sign requests to\neach other and the signing form changed on both sides at once.<\/p>","1.9.0":"<p>Stored data moved to a new prefix and there is no automatic migration &mdash;\nre-pair the site with your hub after updating, and re-enter the backup\npassphrase. Existing backups still need the old passphrase to decrypt.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3661811,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3661811,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3661811,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3661811,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["1.13.7","1.14.0","1.15.0","1.16.0","1.17.0","1.17.1","1.18.0","1.19.0","1.22.2","1.23.0","1.25.0","1.26.0","1.27.0","1.27.1","1.27.2","1.27.3","1.28.0","1.28.2","1.29.1","1.29.3","1.30.0","1.30.1","1.31.1","1.32.0","1.33.0","1.33.1","1.35.0","1.36.0","1.37.0","1.37.1","1.37.2","1.37.3","1.37.4","1.37.5","1.38.0","1.40.2"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3693044,"resolution":"1","location":"assets","locale":"","width":2080,"height":628},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3661811,"resolution":"2","location":"assets","locale":"","width":1120,"height":1468},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3693044,"resolution":"3","location":"assets","locale":"","width":2080,"height":2560},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3661811,"resolution":"4","location":"assets","locale":"","width":2080,"height":1308},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3661811,"resolution":"5","location":"assets","locale":"","width":2080,"height":1150},"screenshot-6.png":{"filename":"screenshot-6.png","revision":3693044,"resolution":"6","location":"assets","locale":"","width":1120,"height":778}},"screenshots":{"1":"The Bulwarden screen at a glance: whether the site is connected, whether\nbackups are encrypted, when the last backup and scan ran, and how current\nthe scanner's signature list is.","2":"Connecting the site: the connect string you paste into your hub, and the\nAPI credentials behind it.","3":"Backups: a run in progress showing the phase it has reached, its log and a\ncancel button, above the encryption passphrase and the folders left out of\nthe archive.","4":"The malware scan: what the last one found, grouped by kind, and how much of\nthe site could be checked against the files WordPress.org published.","5":"The activity log: an append-only record of credential changes, backups,\nrestores, scans and repairs, including the ones the hub triggered.","6":"The dashboard support card \u2014 who maintains this site, how to reach\nthem, and the <em>Request help<\/em> button that files a ticket with them directly."}},"plugin_section":[262246],"plugin_tags":[151,732,1184,5603,600],"plugin_category":[52,54],"plugin_contributors":[277095],"plugin_business_model":[],"class_list":["post-355276","plugin","type-plugin","status-publish","hentry","plugin_section-dashboard-widgets","plugin_tags-backup","plugin_tags-maintenance","plugin_tags-malware","plugin_tags-monitoring","plugin_tags-security","plugin_category-performance","plugin_category-security-and-spam-protection","plugin_contributors-bulwarden","plugin_committers-bulwarden"],"banners":{"banner":"https:\/\/ps.w.org\/bulwarden\/assets\/banner-772x250.png?rev=3661811","banner_2x":"https:\/\/ps.w.org\/bulwarden\/assets\/banner-1544x500.png?rev=3661811","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/bulwarden\/assets\/icon-128x128.png?rev=3661811","icon_2x":"https:\/\/ps.w.org\/bulwarden\/assets\/icon-256x256.png?rev=3661811","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/bulwarden\/assets\/screenshot-1.png?rev=3693044","caption":"The Bulwarden screen at a glance: whether the site is connected, whether\nbackups are encrypted, when the last backup and scan ran, and how current\nthe scanner's signature list is."},{"src":"https:\/\/ps.w.org\/bulwarden\/assets\/screenshot-2.png?rev=3661811","caption":"Connecting the site: the connect string you paste into your hub, and the\nAPI credentials behind it."},{"src":"https:\/\/ps.w.org\/bulwarden\/assets\/screenshot-3.png?rev=3693044","caption":"Backups: a run in progress showing the phase it has reached, its log and a\ncancel button, above the encryption passphrase and the folders left out of\nthe archive."},{"src":"https:\/\/ps.w.org\/bulwarden\/assets\/screenshot-4.png?rev=3661811","caption":"The malware scan: what the last one found, grouped by kind, and how much of\nthe site could be checked against the files WordPress.org published."},{"src":"https:\/\/ps.w.org\/bulwarden\/assets\/screenshot-5.png?rev=3661811","caption":"The activity log: an append-only record of credential changes, backups,\nrestores, scans and repairs, including the ones the hub triggered."},{"src":"https:\/\/ps.w.org\/bulwarden\/assets\/screenshot-6.png?rev=3693044","caption":"The dashboard support card \u2014 who maintains this site, how to reach\nthem, and the <em>Request help<\/em> button that files a ticket with them directly."}],"raw_content":"<!--section=description-->\n<p>Bulwarden is the <strong>site agent<\/strong>, installed on each managed WordPress site. It\nexposes a signed REST API (<code>jgm\/v1<\/code>) that a <strong>Bulwarden hub<\/strong> calls to read\nhealth and security data, run checks and trigger updates. The hub is a separate\napplication \u2014 it is not part of this plugin \u2014 and <strong>this plugin talks to no hub\nat all until you pair it with one<\/strong>. On its own it sends nothing anywhere.<\/p>\n\n<p>It is built for the person who is responsible for a site they did not build:\nan agency looking after forty of them, or an owner who wants one place that\nanswers \"is this thing all right?\" without logging into it.<\/p>\n\n<h4>What it reports<\/h4>\n\n<ul>\n<li><strong>Site health<\/strong> \u2014 WordPress, PHP and plugin versions, pending updates, disk\nusage, and WordPress' own Site Health tests, collected in one snapshot.<\/li>\n<li><strong>A full plugin and theme inventory<\/strong>, with each item's version, whether it\nis active, whether an update is waiting, and whether it came from\nWordPress.org at all \u2014 which is what lets a hub match it against a CVE feed.<\/li>\n<li><strong>Must-use plugins<\/strong>, which WordPress runs on every single request with\nnothing activating them and no way to switch one off from the admin. Nothing\nelse in wp-admin lists them properly, and dropping a file there is the tidiest\nway to install a backdoor.<\/li>\n<li><strong>Configuration-level security checks<\/strong> with a posture score \u2014 file editing,\ndirectory listing, XML-RPC, debug output, and the rest of the settings that\nquietly decide how exposed a site is.<\/li>\n<li><strong>Unfinished updates.<\/strong> An update that already replaced a site's files can\nstill fail to finish, and neither state clears itself: a pending database\nschema upgrade that every administrator is sent to and the public site hides\ncompletely, or a leftover <code>.maintenance<\/code> lock from an updater that died\nmid-copy. Both are reported, and both can be finished from the hub by running\nexactly what WordPress itself would have run.<\/li>\n<li><strong>Email delivery.<\/strong> WordPress keeps no record of the mail a site sends: when\na contact form stops delivering or an order confirmation never arrives, the\nerror is raised and thrown away. This counts what was attempted, what failed\nand why, and reports which transport is doing it \u2014 PHP's <code>mail()<\/code>, an SMTP\nserver, or the plugin that configured one. No part of any message is stored:\nnot the recipient, not the subject, not the body.<\/li>\n<\/ul>\n\n<h4>What it finds<\/h4>\n\n<ul>\n<li><strong>A malware scan in three layers.<\/strong> Every core and WordPress.org file is\nverified against its <em>published<\/em> checksums, so a modified file is found\nwhether or not anybody has written a signature for what was put in it; then\nevery file is read for the code patterns webshells and injected backdoors\nactually use. Findings carry the file, the line and a short excerpt.<\/li>\n<li><strong>Backdoors that arrive dressed as an ordinary plugin<\/strong> \u2014 a forged\nadministrator login, a privileged action gated on a key baked into the file,\nan account created from hard-coded credentials, users hidden from the users\nlist. A plugin absent from WordPress.org has no published files to check it\nagainst, so without this a hostile one surfaced only as <em>unverifiable<\/em>.<\/li>\n<li><strong>A plugin hiding itself.<\/strong> Malware that conceals a plugin does it by\nanswering the question before WordPress can \u2014 and the hidden plugin is then\nalso the one plugin whose files are never checked. The scan counts what is on\ndisk itself and compares.<\/li>\n<li><strong>Server-configuration files.<\/strong> <code>.htaccess<\/code>, <code>php.ini<\/code> and <code>.user.ini<\/code> are\nthe one part of a WordPress install that changes what the <em>server<\/em> does\nrather than what WordPress does, and nothing in wp-admin shows them. A file\nset to run before every request, a rule that makes uploaded images execute as\nPHP, a redirect that only fires for search engines: all reported, with the\nline. Nothing is ever changed for you \u2014 a good share of those directives are\nwritten deliberately by a host or a security plugin, and the point is that\nyou get to decide which.<\/li>\n<li><strong>Leftover files.<\/strong> An interrupted backup, a finished restore and a migration\nplugin all leave things behind and none of them clean up: a\n  wp-config.php.bak the web server hands out as plain text, a <code>database.sql<\/code>\nin the web root, an installer that overwrites the site with no login. Found,\nmeasured, and removable from the hub \u2014 and the <em>site<\/em> decides what may be\ndeleted, so a stale list can only ever be refused.<\/li>\n<li><strong>Signatures that keep up.<\/strong> The scan's pattern list can be updated by your\nprovider without updating this plugin, so a new detection reaches the site in\nhours rather than waiting for a release to travel a whole fleet. A site that\nhas never been paired scans with the list this plugin ships.<\/li>\n<li><strong>A scan that starts itself.<\/strong> When a plugin WordPress.org does not publish\nappears in the plugins or must-use folder, the site starts a scan within\nseconds and tells your provider what set it off, instead of waiting for the\nnext scheduled one. Fake plugins are how most WordPress break-ins arrive, and\na scan is a cheap thing to be wrong about: nothing is blocked, quarantined or\ndeleted \u2014 the site looks, and your provider decides.<\/li>\n<\/ul>\n\n<h4>What it does<\/h4>\n\n<ul>\n<li><strong>Remote core, plugin and theme updates<\/strong>, wrapped in maintenance mode so\nvisitors never see a half-updated site \u2014 and taken off the host's PHP time\nlimit, so a core update on shared hosting finishes instead of being killed\npart-way.<\/li>\n<li><strong>Zero-knowledge encrypted backups.<\/strong> The archive is built <em>and encrypted on\nthis server<\/em>, under a passphrase that is never transmitted anywhere;\nonly the ciphertext is uploaded. Nobody \u2014 including whoever runs the hub \u2014\ncan read a backup without that passphrase.<\/li>\n<li><strong>Restores that fail safely.<\/strong> The same thing in reverse, and it can put back\nthe database alone if that is all that broke. Every table is rebuilt beside\nthe live one and swapped in only once it is complete, so a restore stopped by\na timeout or a server limit leaves the database exactly as it was rather than\nhalf-replaced \u2014 and picks itself back up where it stopped. What could not be\ndone is counted and reported rather than discarded, so a restore that ran into\ntrouble says so instead of reporting success.<\/li>\n<li><strong>Page tracing.<\/strong> Your provider can ask this site to render one of its own\npublic pages and report which file produced each line of the HTML \u2014 core, a\nplugin, the theme, a must-use plugin. It answers the question nothing in\nWordPress answers: <em>which<\/em> plugin adds the Google Fonts tag, or the tracking\nscript, or the markup nobody can find a setting for. The page is never\naltered to do it.<\/li>\n<li><strong>WP-Cron on a schedule that actually runs.<\/strong> A site with no visitors runs no\ncron, which is how scheduled posts, order emails and licence checks quietly\nstop.<\/li>\n<li><strong>A support card on the dashboard<\/strong> naming whoever maintains the site, how to\nreach them, and a <em>Request help<\/em> button that files a ticket with them\ndirectly \u2014 attached to the right site, without the owner having to explain\nwhich one.<\/li>\n<li><strong>An activity log on the site itself<\/strong>, append-only, recording everything that\nchanged this site or sent part of it somewhere \u2014 and which side asked. Updates\ninstalled by your provider and anything that failed with them, backups,\nrestores, scans, repairs, deleted leftovers, a file read off the server, a\ntraced page, and every change to the credentials, the passphrase or the\nprovider details. WordPress records what changed and never who asked, and the\nanswer belongs on your own server rather than only in somebody else's console.<\/li>\n<\/ul>\n\n<h4>Pairing<\/h4>\n\n<p>On activation the plugin provisions an API key + secret. Copy the <strong>connect\nstring<\/strong> from the <strong>Bulwarden<\/strong> entry in the WordPress admin menu and paste\nit into the hub's \"Connect a site\" form. Your provider's support details are\nthen pushed to the site automatically by the hub and appear on the dashboard.<\/p>\n\n<p>No hub account yet? Until the site is paired, the plugin's banner and settings\nscreen offer <strong>Start a free trial<\/strong>, which opens the Bulwarden hub's pricing\npage (https:\/\/hub.bulwarden.net\/pricing\/) in a new tab. Start a trial there,\nset your password from the email you receive, choose \"Connect a site\" in the\nhub and paste the connect string. Opening that page sends nothing from this\nsite; it is an ordinary link.<\/p>\n\n<h4>Security<\/h4>\n\n<p>Hub \u2192 client requests are authenticated with the per-site API key and an\nHMAC-SHA256 signature over the method, route, timestamp, a single-use nonce and\nthe body. The nonce is what makes a signature good exactly once: it is recorded\nwhen the request arrives and refused if it comes back, so a captured request\ncannot be replayed. Timestamps outside a 5-minute window are rejected outright,\nwhich is also how long a nonce has to be remembered for. The secret never\ntravels on the wire \u2014 it lives only in this plugin and in the hub webapp.<\/p>\n\n<h3>Privacy<\/h3>\n\n<p>Bulwarden is a client agent: on its own it sends data nowhere. Pairing this\nsite with a hub (yours, or your maintenance provider's) is an explicit,\nopt-in action \u2014 you choose when to copy the connect string from the Bulwarden\nscreen in the WordPress admin menu and hand it to that hub. Nothing is\ntransmitted before that.<\/p>\n\n<p>Once paired, data only moves in these cases:<\/p>\n\n<ul>\n<li><strong>Health, inventory, and security checks.<\/strong> The paired hub reads these from\nthis site's signed REST API when it polls or when a check is requested \u2014\nWordPress\/plugin\/theme versions and update status, a full plugin &amp; theme\ninventory, disk usage, and the configuration-level security posture score.<\/li>\n<li><strong>Backups<\/strong>, only when triggered (the Settings screen's \"Back up now\"\nbutton, or a request from the paired hub): the full site is archived and\nencrypted on this server with a passphrase that is never transmitted\nanywhere, then the ciphertext is uploaded to the hub. Only someone who has\nthe passphrase can decrypt a backup \u2014 the hub operator cannot read its\ncontents.<\/li>\n<li><strong>Malware scan results<\/strong>, only when a scan is triggered (admin- or\nhub-initiated): findings (type, severity, file path, line, and a short\nexcerpt) are sent to the hub that requested the scan.<\/li>\n<li><strong>Finishing an interrupted update<\/strong>, when the paired hub asks. Nothing is\nsent in that exchange; the site runs WordPress' own database upgrade, or\ndeletes an expired <code>.maintenance<\/code> lock file, and reports the outcome.<\/li>\n<li><strong>Server-configuration files<\/strong>, when the paired hub asks: the contents of\nthis site's <code>.htaccess<\/code>, <code>php.ini<\/code> and <code>.user.ini<\/code> files, so the hub can show\nwhat is in them and when they last changed. These are configuration, not\ncontent \u2014 no post, page, media file or user data is read.<\/li>\n<li><strong>Email counters<\/strong>, as part of the health snapshot: how many messages this\nsite attempted and how many failed over the last seven days, the error text\nthe mail server returned, and which transport is sending. <strong>No part of any\nmessage is recorded<\/strong> \u2014 not the recipient, not the subject, not the body.<\/li>\n<li><strong>A traced page<\/strong>, only when the hub asks for one: the HTML of a single\npublic page of this site, exactly as an anonymous visitor would receive it,\nplus the file paths and line numbers that produced it. No file contents are\nread and no logged-in view is ever rendered.<\/li>\n<\/ul>\n\n<p>Independently of any hub pairing, this plugin verifies WordPress core and\nwordpress.org-hosted plugin files by comparing local file hashes against the\npublic checksum APIs at <code>api.wordpress.org<\/code> and <code>downloads.wordpress.org<\/code>.\nThose requests carry only what identifies the release to look up \u2014 the\nWordPress version and locale, and each plugin's slug and version. No file\ncontents, hashes, URLs or user data are sent: the reference checksums come\nback and the comparison happens here, on this server.<\/p>\n\n<p>Bulwarden does not call home on activation, does not collect analytics, and\nstores data on no server this site hasn't been explicitly connected to. The\nhub itself is a separate service outside this plugin's control; consult your\nprovider for how it handles the data described above.<\/p>\n\n<h3>External services<\/h3>\n\n<p>This plugin connects to the services below. Nothing here runs on activation.<\/p>\n\n<h4>Your Bulwarden hub<\/h4>\n\n<p>Bulwarden is the site-side agent for a <strong>hub<\/strong> \u2014 the management application an\nagency or site owner runs to watch and maintain a fleet of WordPress sites. The\nhub is a separate product and is not part of this plugin.<\/p>\n\n<p>The default hosted hub is operated by Bulwarden at https:\/\/bulwarden.net\/ \u2014\nterms of use: https:\/\/bulwarden.net\/terms\/ , privacy policy:\nhttps:\/\/bulwarden.net\/privacy\/ . A hub can also be self-hosted, or run by the\nmaintenance provider you buy from, in which case this plugin talks to that\nprovider's server instead and their terms and privacy policy apply.<\/p>\n\n<p><strong>No connection to any hub exists until you make one.<\/strong> You pair a site by\ncopying the connect string from the Bulwarden screen in the WordPress admin\nmenu and pasting it into the hub; before that the plugin contacts no hub at\nall, and it never discovers or chooses one on its own. Once paired, data moves only in these cases:<\/p>\n\n<ul>\n<li><strong>When the hub asks for status.<\/strong> It reads this site's signed REST API and\nreceives WordPress\/plugin\/theme versions and update status, the full plugin\nand theme inventory, disk usage, the results of WordPress' own Site Health\ntests, and the configuration-level security posture score.<\/li>\n<li><strong>When a backup runs<\/strong> (you press \"Back up now\", or the hub requests one).\nThe site is archived and encrypted here, with a passphrase that is never\ntransmitted, and only the ciphertext is uploaded. The hub operator cannot\nread a backup's contents without that passphrase.<\/li>\n<li><strong>When a malware scan runs<\/strong> (started by you or by the hub). The findings \u2014\ntype, severity, file path, line number and a short excerpt \u2014 are sent to the\nhub that asked for the scan.<\/li>\n<li><strong>When the hub pushes your provider's support details<\/strong> \u2014 their name, logo,\nsupport address and accent colour \u2014 on connect and whenever they change\nthem. Nothing is sent from the site in that exchange.<\/li>\n<\/ul>\n\n<p>Every one of those requests is authenticated with this site's own API key and\nan HMAC-SHA256 signature; the shared secret never travels over the wire.<\/p>\n\n<h4>api.wordpress.org and downloads.wordpress.org<\/h4>\n\n<p>The malware scan verifies WordPress core and wordpress.org-hosted plugins\nagainst their published checksums, using WordPress.org's own public APIs at\n    https:\/\/api.wordpress.org\/core\/checksums\/1.0\/ and\n    https:\/\/downloads.wordpress.org\/plugin-checksums\/. This happens whenever a\nscan runs, whether or not the site is paired with a hub.<\/p>\n\n<p>These requests identify only the release being looked up: the WordPress version\nand locale, and each installed plugin's slug and version. No file contents,\nhashes, site URL or user data are sent \u2014 the reference checksums are returned\nand compared locally.<\/p>\n\n<p>These are WordPress.org services, covered by the WordPress.org privacy policy:\nhttps:\/\/wordpress.org\/about\/privacy\/<\/p>\n\n<!--section=installation-->\n<p>Bulwarden is one half of a pair: this plugin runs on the site, and a <strong>hub<\/strong>\nruns somewhere else and manages it. Installing the plugin on its own does\nnothing until you pair it with a hub.<\/p>\n\n<ol>\n<li>Install and activate the plugin as you would any other \u2014 upload it under\nPlugins \u2192 Add New \u2192 Upload Plugin, or install it from the directory.<\/li>\n<li>Go to <strong>Bulwarden<\/strong> in the WordPress admin menu. The plugin generated an\nAPI key and secret for this site on activation; the screen shows a single\n<strong>connect string<\/strong> that carries the site name, URL and both credentials.<\/li>\n<li>Copy that string and paste it into your hub's \"Connect a site\" form. That is\nthe whole pairing step \u2014 there is nothing to type on this side, and no\nhub address to configure here.<\/li>\n<li>If you want encrypted backups, set a <strong>backup passphrase<\/strong> on the same\nscreen. It never leaves this server, and it is required to restore. Keep it\nsomewhere you will still have it after the disaster you are backing up\nagainst \u2014 nobody, including your hub operator, can recover a backup\nwithout it.<\/li>\n<\/ol>\n\n<h4>Do I need a hub?<\/h4>\n\n<p>Yes, for anything beyond installing it. The hub is separate software: use the\nhosted one, run your own, or use the one your maintenance provider runs. Until\nthe site is paired, this plugin contacts nothing and does nothing.<\/p>\n\n<h4>Upgrading<\/h4>\n\n<p>Updates install like any other plugin. Your credentials, passphrase and settings\nsurvive an update; nothing needs re-pairing.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"does%20this%20plugin%20send%20my%20site%27s%20data%20anywhere%20on%20its%20own%3F\"><h3>Does this plugin send my site's data anywhere on its own?<\/h3><\/dt>\n<dd><p>No. Before you pair it with a hub it contacts nothing at all, and it never\ndiscovers or chooses a hub by itself. After pairing, data moves only for the\nthings listed under Privacy below \u2014 health checks, backups, malware scan\nfindings \u2014 and only to the hub you paired with.<\/p><\/dd>\n<dt id=\"can%20the%20hub%20operator%20read%20my%20backups%3F\"><h3>Can the hub operator read my backups?<\/h3><\/dt>\n<dd><p>No, provided you set a passphrase. The archive is encrypted <strong>on this server<\/strong>\nwith a key derived from your passphrase, and only the ciphertext is uploaded.\nThe passphrase is never transmitted, so a hub holds bytes it cannot decrypt.\nThe same property is why nobody can recover a lost passphrase for you.<\/p><\/dd>\n<dt id=\"what%20happens%20if%20i%20deactivate%20or%20delete%20the%20plugin%3F\"><h3>What happens if I deactivate or delete the plugin?<\/h3><\/dt>\n<dd><p>The site stops answering its hub, which the hub will notice and report as the\nagent going unreachable. Deleting the plugin removes its settings, its audit-log\ntable and its working directories (see <code>uninstall.php<\/code>); backups already stored\non a hub are unaffected.<\/p><\/dd>\n<dt id=\"is%20the%20plugin%20available%20in%20german%3F\"><h3>Is the plugin available in German?<\/h3><\/dt>\n<dd><p>Yes. Everything the plugin puts on screen \u2014 the settings page, the support\ncard on the dashboard, every status message and every scan finding \u2014 is\ntranslated, and WordPress picks it up from your site's own language setting with\nnothing to configure. Both German locales are covered: <em>Deutsch<\/em> and <em>Deutsch\n(Sie)<\/em>. The wording is formal throughout, because these screens are read by the\nowner of the site rather than by the agency that maintains it.<\/p><\/dd>\n<dt id=\"does%20it%20work%20on%20multisite%3F\"><h3>Does it work on multisite?<\/h3><\/dt>\n<dd><p>The plugin runs per site. A network-wide database upgrade is reported but not\nperformed \u2014 core's own network upgrade screen walks every site in the\nnetwork, and that is where it belongs.<\/p>\n\n<p>Network activation is not supported: it sets the plugin up for the main site\nonly, and every other site in the network would have no credentials to be\npaired with. Activate it on each site you want managed, from that site's own\nPlugins screen, and pair each one with the hub separately.<\/p><\/dd>\n<dt id=\"why%20does%20the%20malware%20scan%20flag%20a%20file%20i%20know%20is%20fine%3F\"><h3>Why does the malware scan flag a file I know is fine?<\/h3><\/dt>\n<dd><p>The file-integrity check compares every core and WordPress.org-hosted file\nagainst the checksums WordPress.org published for that exact release. A caching\nor optimisation plugin that rewrites a stylesheet produces the same mismatch a\nbackdoor does, which is why \"this file no longer matches what was published\" is\nreported as a fact rather than a verdict, and why non-executable files are\nwarnings rather than critical findings. Your hub can show you the actual diff.<\/p><\/dd>\n<dt id=\"what%20does%20it%20do%20to%20my%20site%27s%20performance%3F\"><h3>What does it do to my site's performance?<\/h3><\/dt>\n<dd><p>Nothing on page loads: there is no front-end code path. Backups and malware\nscans are real work and run in the background across WordPress cron ticks, to a\ntime budget, so they do not block visitors. Health checks are answered on demand\nwhen the hub asks.<\/p><\/dd>\n<dt id=\"which%20php%20and%20wordpress%20versions%20are%20supported%3F\"><h3>Which PHP and WordPress versions are supported?<\/h3><\/dt>\n<dd><p>PHP 7.4 or newer and WordPress 6.0 or newer. Backups additionally need the\n    sodium extension (encryption) and either <code>zlib<\/code> or the <code>zip<\/code> extension\n(archiving); the Settings screen tells you if either is missing.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.40.2<\/h4>\n\n<ul>\n<li>Maintenance: internal code-style fixes only, no change to how the plugin behaves.<\/li>\n<\/ul>\n\n<h4>1.40.1<\/h4>\n\n<ul>\n<li>Improved: the plugin lock now also covers must-use plugins. When a site is locked, a must-use plugin dropped in after the lock is moved out of the mu-plugins folder instead of being allowed to run.<\/li>\n<\/ul>\n\n<h4>1.40.0<\/h4>\n\n<ul>\n<li>New: your maintenance provider can set a site to \"lock\" plugins \u2014 no new plugins can be installed or activated at all. Existing plugins keep working and still receive updates, and you can still deactivate a plugin; only adding or switching on a new one is blocked. This is off unless your provider turns it on.<\/li>\n<\/ul>\n\n<h4>1.39.0<\/h4>\n\n<ul>\n<li>New: if a plugin that is not from the WordPress.org directory appears on your site and then crashes every page, Bulwarden now switches it off by itself and brings the site back, instead of leaving it down until someone notices. It only does this to a plugin that arrived in the last few days; the files are left in place so they can be examined.<\/li>\n<li>New: your maintenance provider can set your sites to hold a new plugin that is not from the WordPress.org directory \u2014 switching it off the moment it appears and turning it back on only once a scan is clean, or only once the provider approves it. This is off unless your provider turns it on. A plugin you install yourself is held for a moment, not blocked.<\/li>\n<li>Improved: the malware scan now recognises a plugin that creates a hidden account and gives it administrator powers while calling it an \"editor\".<\/li>\n<\/ul>\n\n<h4>1.38.1<\/h4>\n\n<ul>\n<li>Improved: the malware scan now recognises a fake plugin that creates a hidden account with full administrator rights while calling it an \"editor\", so it does not look like a new administrator. This is what the fake \"maintenance toolkit\" plugins installed on several sites in October 2026 do. Such a plugin is now reported as malware.<\/li>\n<\/ul>\n\n<h4>1.38.0<\/h4>\n\n<ul>\n<li>New: the malware scan now finds PHP code hidden in \".cache\" files, and in any file that an .htaccess tells the server to run as PHP whatever its name. This is how the malware in the compromised Rank Math SEO PRO 3.0.123 update stays on a site, and until now the scan never opened those files. Such a file is reported as a warning, or as critical when the server is set up to run it or the code looks like an attack. Cache files that only store data are not reported.<\/li>\n<li>New: the server-configuration check now reads .htaccess rules that span several lines, and says which kinds of file a rule makes the server run as PHP. A rule that makes data, cache or image files run as PHP is reported as critical.<\/li>\n<li>New: the malware scan reports a folder in your site's main directory that has the same name as one of your published pages (for example \"about-us\") and contains an index file. The web server shows that folder instead of your real page, which is how some spam kits take over a page without touching WordPress. It is a warning, or critical when the folder shows search engines something different from visitors.<\/li>\n<\/ul>\n\n<h4>1.37.5<\/h4>\n\n<ul>\n<li>Improved: if you installed Bulwarden yourself from the plugin directory, the \"not connected yet\" banner, the setup steps on the Bulwarden screen and the dashboard card now offer a \"Start a free trial\" button. It opens the Bulwarden hub in a new tab, where you can start a trial and then connect this site with its connect string. Nothing is sent from your site when you click it. If a provider asked you to install the plugin, connect the site to them as before; the button disappears once the site is connected.<\/li>\n<\/ul>\n\n<h4>1.37.4<\/h4>\n\n<ul>\n<li>Fixed: on sites behind a page cache with few visitors, a malware scan could take a day or more to finish, because each step of the scan waited for somebody to visit the site before the next one started. The scan now starts its next step itself, so it finishes in minutes. Restores already worked this way and now share the same mechanism.<\/li>\n<li>Improved: malware scans are much faster on sites with large media libraries. Images, videos, PDFs and other media files in the uploads folder are still checked for hidden PHP code, but the full set of malware signatures is only run on them when they actually contain PHP. On a test site with 9,000 images a scan went from five minutes to under two.<\/li>\n<\/ul>\n\n<h4>1.37.3<\/h4>\n\n<ul>\n<li>Fixed: on sites where another plugin or a code snippet restricts the WordPress REST API to logged-in users (for example the \"Disable REST API\" option in Admin and Site Enhancements), every request from the maintenance hub was refused before Bulwarden could check it, so the site could not be paired, monitored or backed up. Requests signed by the hub are now let through that restriction, for Bulwarden's own routes only; the rest of the REST API stays restricted, and nobody is logged in.<\/li>\n<\/ul>\n\n<h4>1.37.2<\/h4>\n\n<ul>\n<li>Fixed: the malware scan was meant to look for PHP files in the cache folder (wp-content\/cache) since 1.35.0, but on every site using the default backup settings it never did, because that folder is left out of backups by default. Leaving the cache out of backups no longer keeps the scan out of it. Only PHP files there are checked, so the scan does not slow down on a large page cache. If you want part of the cache left alone, exclude that subfolder instead.<\/li>\n<\/ul>\n\n<h4>1.37.1<\/h4>\n\n<ul>\n<li>Fixed: on sites with large media folders the malware scan could keep re-reading the same folder and never finish. A folder that needs more than one pass now continues where the previous pass stopped, so every file is read once.<\/li>\n<li>Fixed: if the web host stopped the scan's background process abruptly, the scan was left half-finished with nothing to resume it. The scan now arranges its next step before it starts working, so it picks up again by itself within ten minutes.<\/li>\n<\/ul>\n\n<h4>1.37.0<\/h4>\n\n<ul>\n<li>New: your maintenance provider can ask the plugin what it is doing right now \u2014 whether a backup, malware scan or restore is running, how far it has got, and whether the scheduled tasks it waits for are running on time. Nothing is started or changed by asking, and no passwords, keys or file contents are included in the answer.<\/li>\n<\/ul>\n\n<h4>1.36.1<\/h4>\n\n<ul>\n<li>Fixed: signing in with one click from your maintenance provider could end on the message \"This sign-in link is not valid any more\" even though the sign-in had worked \u2014 on sites where you were already signed in, and on sites using a custom login address. The link is still used exactly once; a browser that already holds an administrator session is now taken straight to the dashboard instead of being shown the error.<\/li>\n<\/ul>\n\n<h4>1.36.0<\/h4>\n\n<ul>\n<li>New: your maintenance provider can check more than the homepage after an automatic update. The plugin lists the site's pages \u2014 shop pages such as the checkout and the cart are marked \u2014 so the provider can pick the ones that matter, and each is compared before and after the update.<\/li>\n<li>New: for pages that only show their real content to a signed-in visitor, such as a customer account, the provider can ask the site to create a read-only account called bulwarden-check. It can do nothing but read, cannot sign in with a password, and is only ever signed in for a few minutes by the automatic check. If its role is ever given more rights, the site stops signing in as it. Deleting the account switches this off.<\/li>\n<li>Every creation of that account and every check sign-in is recorded in the activity log.<\/li>\n<\/ul>\n\n<h4>1.35.0<\/h4>\n\n<ul>\n<li>New: the malware scan now checks the <code>wp-content\/cache<\/code> folder for PHP files with code in them. Attackers who exploit a caching plugin write their web shells there, and the scan used to skip the folder entirely. Only PHP files are read, so a large page cache costs little; generated pages, stylesheets and images are never opened. Every PHP file found there is also checked against the full signature table.<\/li>\n<li>Compiled Blade views (Sage\/Acorn), compiled Twig templates (Timber) and generated data files are recognised and not reported.<\/li>\n<li>A very large cache is reported as not fully checked, rather than treated as clean.<\/li>\n<\/ul>\n\n<h4>1.34.1<\/h4>\n\n<ul>\n<li>Maintenance release: code-style fixes only, with no change in behaviour.\nIt carries everything listed under 1.34.0.<\/li>\n<\/ul>\n\n<h4>1.34.0<\/h4>\n\n<ul>\n<li>Security: <strong>a long backup can no longer undo settings changed while it\nran.<\/strong> Regenerating the connection credentials, setting a backup\npassphrase or a change of hub address during a backup used to be reverted\nwhen the backup finished.<\/li>\n<li>Security: signed requests from your agency's hub now refuse anything added\nto the address that the signature does not cover, and each request stays\nsingle-use for its whole validity window.<\/li>\n<li>Security: <strong>the site keeps more secrets out of what it sends your agency.<\/strong>\nScan excerpts from configuration copies, database dumps, exports and logs\nare redacted; password files and connection strings in server\nconfiguration are no longer sent; files that look like keys, dumps or\nbackups are never handed out for a comparison; and a failed restore's\nerror messages no longer quote the data that failed.<\/li>\n<li>Fix: <strong>restores now finish correctly on Windows hosting<\/strong> and other servers\nthat store table names in lower case. Tables with capital letters in\ntheir name (Wordfence's, for example) kept their old contents before.<\/li>\n<li>Fix: the site shows its maintenance notice while a restore writes files\nback, not only while it writes the database.<\/li>\n<li>Fix: a malware scan that takes longer than an hour is no longer restarted\nfrom the beginning every hour, and a new scan no longer runs alongside the\none it replaced.<\/li>\n<li>Fix: the leftover-file check no longer offers a running restore's working\nfolder for deletion.<\/li>\n<li>Hardening: a restore's working files now have unpredictable names, and the\ncopy of this site's connection details it keeps is removed as soon as it\nhas been used.<\/li>\n<li>Fix: a restore that runs out of disk space now stops and says so, instead\nof reporting half-written files as restored.<\/li>\n<li>Fix: a restore that fails part-way no longer leaves a second copy of the\ndatabase behind in temporary tables, and backups never include them or a\nrestore's working folder.<\/li>\n<li>Fix: a malware scan that runs out of memory on the site, or cannot save its\nprogress, is now reported as failed instead of never finishing.<\/li>\n<li>Fix: restores protect this plugin's own folder and working folders on sites\nwith a customised folder layout, and a relative <code>auto_prepend_file<\/code> that\nworks is no longer switched off after a restore.<\/li>\n<li>Security: the site only sends backups and scan results to, and fetches\nrestores from, the hub it is paired with; a restore rejects a backup file\nwith impossible sizes before reading it; signed requests and one-click\nsign-in links can no longer be used twice by two requests arriving at the\nsame moment; the health report no longer lists subscribers and shop\ncustomers; and deleting the plugin now also removes its working folders.<\/li>\n<li>Multisite: network activation is not supported \u2014 activate the plugin on\neach site you want managed.<\/li>\n<\/ul>\n\n<h4>1.33.1<\/h4>\n\n<ul>\n<li>Security: <strong>malware scan excerpts sent to your agency no longer include\npasswords in a few more cases<\/strong> the earlier protection missed: settings\nstored as plain text, passwords written in capital letters, passwords\ncontaining an escaped quote, and logins inside web addresses. Posts\nbehind a password, and form entries some plugins store as posts, are now\ntreated as private.<\/li>\n<li>Fix: an interrupted plugin update can no longer make a folder-specific\nscan rule run on every file.<\/li>\n<\/ul>\n\n<h4>1.33.0<\/h4>\n\n<ul>\n<li>New: <strong>the malware scan now reports program code added to a plugin's or the\nactive theme's folder.<\/strong> For every plugin published on WordPress.org, and for\nthe active theme, the scan already checks that each file the release contains\nis unchanged; it now also reports a PHP file in that folder that the release\ndoes not contain at all, which is where an attacker hides a backdoor next to\ngenuine files. Empty guard files and protected data files are not reported.<\/li>\n<\/ul>\n\n<h4>1.32.0<\/h4>\n\n<ul>\n<li>New: <strong>the malware scan now reports PHP files with code in the uploads\nfolder.<\/strong> Attacks through vulnerable file-upload features in plugins almost\nalways leave a small PHP file there, and it stays behind after the plugin\nis updated. Harmless files that plugins put there on purpose, such as empty\n\"Silence is golden\" files, protected data files and known plugin files like\nBorlabs Cookie's, are not reported.<\/li>\n<\/ul>\n\n<h4>1.31.1<\/h4>\n\n<ul>\n<li>Security: a password in <code>wp-config.php<\/code> or in a site setting is now always\nleft out of scan findings, however short it is. Before, a password shorter\nthan six characters could appear in a finding.<\/li>\n<li>Fix: if an update of this plugin was interrupted halfway, leaving old and\nnew files side by side, a malware scan could stop with an error. It now\nfinishes with the plugin's own detection rules until the update completes.<\/li>\n<\/ul>\n\n<h4>1.31.0<\/h4>\n\n<ul>\n<li>New: <strong>the malware scan now also checks the database.<\/strong> Many attacks never\nleave a file behind: they put a script into a post, hide code in a site\nsetting, create an extra administrator or schedule a task that brings the\nattack back. The scan now looks at posts, settings, user accounts and\nscheduled tasks, and reports an administrator account that exists but is\nhidden from the Users screen.<\/li>\n<li>New: <strong>your provider can now respond to a new attack within hours instead\nof waiting for a plugin update.<\/strong> Besides the code patterns it already\nsends, it can now send the file names an attack drops, extra types of\nprogram file to report, patterns for the database checks above, and rules\nthat apply only to certain folders or file types.<\/li>\n<li>New: a scan now always uses your provider's latest detection rules. Before,\na site could run for up to six hours on the previous set.<\/li>\n<li>Security: findings from <code>wp-config.php<\/code> and from the database never include\npasswords, keys or other secrets. The finding still shows what matched,\nwith the secret replaced by its length.<\/li>\n<\/ul>\n\n<h4>1.30.1<\/h4>\n\n<ul>\n<li>Fix: one malware signature, the one that looks for <code>preg_replace()<\/code> with the\nlong-removed <code>\/e<\/code> modifier, could take far longer than it should on files\ncontaining long regular expressions, and on some files gave up without an\nanswer. It now checks those files quickly and reliably, and still finds\nwhat it is meant to find.<\/li>\n<\/ul>\n\n<h4>1.30.0<\/h4>\n\n<ul>\n<li>New: <strong>the malware scan now recognises files that known attacks leave\nbehind by their name.<\/strong> Some exploits drop a marker or a web shell under a\nfixed name, and the name is often the only thing about the file that never\nchanges. The first entries cover the WordPress file-inclusion attack\n(CVE-2026-87902) seen in September 2026.<\/li>\n<li>New: <strong>the scan now also looks in the server's temporary folders<\/strong> (<code>\/tmp<\/code>,\n  \/var\/tmp, <code>\/dev\/shm<\/code>), where that attack parks its payload before using\nit. Only files your own site created are looked at, so on shared hosting\nother customers' files are never read or reported. A PHP file there that\nmatches no known signature is reported as information only, because some\ntemplate engines legitimately keep compiled files there.<\/li>\n<\/ul>\n\n<h4>1.29.3<\/h4>\n\n<ul>\n<li>Fix: <strong>a restore that could not write some files still reported a clean\nfinish.<\/strong> A folder the web server had lost write access to meant dozens or\nhundreds of files quietly not restored, under a green \"Finished\" badge. The\nfiles it deliberately leaves alone \u2014 your <code>wp-config.php<\/code>, the maintenance\nplugin itself, WordPress' drop-ins \u2014 still count as a clean restore, as they\nshould; the ones it tried and failed to write now say so.<\/li>\n<li>Fix: <strong>\"remove files the backup does not contain\" now says which folder it\ncould not clean out.<\/strong> It deliberately never empties a folder your backup has\nnothing for \u2014 otherwise a backup taken without your plugins folder would\ndelete every plugin on the site \u2014 but it only mentioned that in the run log.\nSince this is the option you would use to clear out a hack, and must-use\nplugins are where one usually hides, the restore now names the folder it\nskipped where you will see it.<\/li>\n<li>Fix: <strong>a file that is a shortcut (symlink) is now listed when a backup skips\nit.<\/strong> Backups never follow shortcuts, which is correct, and skipped folders\nwere already listed; skipped <em>files<\/em> were not \u2014 so a site whose uploads or\nconfiguration files are linked in from elsewhere had them missing from the\nbackup with nothing saying so.<\/li>\n<li>Fix: a backup that failed to download during a restore reported the raw\nnetwork error. It now says the backup could not be fetched and that nothing\non the site was changed, with the technical detail after it.<\/li>\n<\/ul>\n\n<h4>1.29.2<\/h4>\n\n<ul>\n<li>Fix: <strong>a backup corrupted every percent sign in your site's content and\nsettings, and a restore put the corruption back.<\/strong> Each <code>%<\/code> was written into\nthe backup as a 66-character marker, so prices like \"50% off\", CSS widths and\npercent-encoded links came back mangled \u2014 and any stored setting that\ncontained one (widget configuration, theme options, many plugins' settings)\nbecame unreadable and was silently lost, on a restore that reported success.\nBackups taken from this release on are correct; ones taken before it still\ncarry the fault.<\/li>\n<li>Fix: <strong>a restore could stop part-way through the database and leave the site\nhalf-restored.<\/strong> A table whose full name was 52 to 55 characters long could\nnot be swapped into place, which ended the restore with some tables replaced\nand the rest not. It mostly affected sites with a custom table prefix and\nWooCommerce installed. Such a table is now restored directly and named in the\nreport, as three other cases already were.<\/li>\n<li>Fix: asking a site to restore while it was already busy with a backup or\nrestore raised a PHP fatal error instead of politely declining, so the site\nlogged a critical error and your maintenance provider was told the site had\nbroken rather than that it was busy.<\/li>\n<\/ul>\n\n<h4>1.29.1<\/h4>\n\n<ul>\n<li>Maintenance: coding-standard clean-up in the one-click sign-in code and\nthe settings defaults. No behaviour changes.<\/li>\n<\/ul>\n\n<h4>1.29.0<\/h4>\n\n<ul>\n<li>New: <strong>one-click sign-in from your maintenance provider.<\/strong> Your provider can\nopen this site's admin as an administrator without a password, over the same\nsigned connection it already uses to update and back up the site. Each\nsign-in uses a link that works exactly once, for one minute, for one named\nadministrator, and is written to the activity log with the account it used.\nA switch on the Bulwarden screen, beside the credentials, turns it off \u2014 and\nturning it off closes the route, not only the button.<\/li>\n<\/ul>\n\n<h4>1.28.2<\/h4>\n\n<ul>\n<li>Fix: <strong>the progress of a file restore is now reported correctly when it\nfinishes.<\/strong> The step that writes a backup's files back ended by reporting\nthat it had written none of them \u2014 a completed restore showed\n\"0 of 93,680 files, 0%\" beside a count of the files it had just written.\nThe two figures were also counted differently: the total included the\nbackup's database dump and its description alongside the files, so it could\nnever be reached however many files were restored. It now counts the\nbackup's files, and every one of them is reported either as written or as\ndeliberately left alone \u2014 this site's own wp-config.php, Bulwarden\nitself, and WordPress' drop-ins are never overwritten, and your hub now says\nso instead of leaving the difference unexplained.<\/li>\n<\/ul>\n\n<h4>1.28.1<\/h4>\n\n<ul>\n<li>Fix: <strong>a restore no longer spends most of its time waiting to be asked to\ncarry on.<\/strong> A restore runs in rounds, and at the end of each one the plugin\nasked WordPress' scheduler to come straight back for the next \u2014 except that\nthe request was made from inside the scheduler, which refuses it. So the work\nonly continued when a visitor happened to arrive, or after a three-minute\nsafety timer, and on a quiet site \u2014 which is most sites being restored \u2014 a\nrestore took hours of waiting for minutes of work. On sites where WordPress'\nown scheduler is switched off in favour of a real cron job, which is the\nusual advice for a site that matters, nothing continued the restore at all\nbetween those cron runs. Each round now asks for the next one directly.<\/li>\n<\/ul>\n\n<h4>1.28.0<\/h4>\n\n<ul>\n<li>New: <strong>a banner on a site that has the plugin but is not connected to\nanyone.<\/strong> Until a site is paired with a maintenance hub the plugin does\nnothing at all \u2014 no backups, no scans, nothing watched \u2014 and from the\ndashboard that is indistinguishable from a plugin quietly working. Anyone who\ncan install plugins now sees a notice saying so, with the way to connect the\nsite one click away, and it can be put off for a month at a time. The support\nwidget on the dashboard no longer says Bulwarden maintains a site that nobody\nhas agreed to maintain.<\/li>\n<li>New: <strong>a way to ask Bulwarden for help when there is no maintenance provider\nat all.<\/strong> The Request help button on the dashboard writes to whoever looks\nafter the site, which is no use to somebody who installed this and has nobody.\nThe plugin's own settings screen now carries a short form that reaches\nBulwarden directly, and says exactly what it sends: the site's address, its\nWordPress and PHP versions and the reply address typed into it. Nothing else,\nand no keys.<\/li>\n<li>New: <strong>the hub is told whether a backup encryption passphrase has been set<\/strong> \u2014\nthe fact that there is one, never the passphrase itself, which stays on this\nsite and is never sent anywhere. Without one every backup stops before it\nstarts, and an agency previously found that out from a failed run at three in\nthe morning rather than from the setup nobody finished.<\/li>\n<li>New: <strong>five more malware detections<\/strong>, from a study of 400,000 production\nwebservers (Kasturi et al., <em>Mistrust Plugins You Must<\/em>): code that redefines\na built-in PHP function instead of loading the real one, a function name\nassembled from fragments and then called, a file written to disk, made\nexecutable and run, code that deletes the file it ran from, and the branding\n\"nulled\" plugin marketplaces stamp into the paid plugins they give away with\nsomething added.<\/li>\n<\/ul>\n\n<h4>Earlier versions<\/h4>\n\n<p>The changelog for 1.27.3 and earlier is in changelog.txt, shipped with the plugin.<\/p>","raw_excerpt":"Site agent for health monitoring, malware scanning, encrypted backups and remote updates, driven by a Bulwarden hub you connect it to.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/mlt.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/355276","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mlt.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/mlt.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/mlt.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=355276"}],"author":[{"embeddable":true,"href":"https:\/\/mlt.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/bulwarden"}],"wp:attachment":[{"href":"https:\/\/mlt.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=355276"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/mlt.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=355276"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/mlt.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=355276"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/mlt.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=355276"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/mlt.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=355276"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/mlt.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=355276"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}