Bulwarden

Plugin Banner

Bulwarden

by jgloeckl.de

Download
Description

Bulwarden is the site agent, installed on each managed WordPress site. It
exposes a signed REST API (jgm/v1) that a Bulwarden hub calls to read
health and security data, run checks and trigger updates. The hub is a separate
application — it is not part of this plugin — and this plugin talks to no hub
at all until you pair it with one
. On its own it sends nothing anywhere.

It is built for the person who is responsible for a site they did not build:
an agency looking after forty of them, or an owner who wants one place that
answers “is this thing all right?” without logging into it.

What it reports

  • Site health — WordPress, PHP and plugin versions, pending updates, disk
    usage, and WordPress’ own Site Health tests, collected in one snapshot.
  • A full plugin and theme inventory, with each item’s version, whether it
    is active, whether an update is waiting, and whether it came from
    WordPress.org at all — which is what lets a hub match it against a CVE feed.
  • Must-use plugins, which WordPress runs on every single request with
    nothing activating them and no way to switch one off from the admin. Nothing
    else in wp-admin lists them properly, and dropping a file there is the tidiest
    way to install a backdoor.
  • Configuration-level security checks with a posture score — file editing,
    directory listing, XML-RPC, debug output, and the rest of the settings that
    quietly decide how exposed a site is.
  • Unfinished updates. An update that already replaced a site’s files can
    still fail to finish, and neither state clears itself: a pending database
    schema upgrade that every administrator is sent to and the public site hides
    completely, or a leftover .maintenance lock from an updater that died
    mid-copy. Both are reported, and both can be finished from the hub by running
    exactly what WordPress itself would have run.
  • Email delivery. WordPress keeps no record of the mail a site sends: when
    a contact form stops delivering or an order confirmation never arrives, the
    error is raised and thrown away. This counts what was attempted, what failed
    and why, and reports which transport is doing it — PHP’s mail(), an SMTP
    server, or the plugin that configured one. No part of any message is stored:
    not the recipient, not the subject, not the body.

What it finds

  • A malware scan in three layers. Every core and WordPress.org file is
    verified against its published checksums, so a modified file is found
    whether or not anybody has written a signature for what was put in it; then
    every file is read for the code patterns webshells and injected backdoors
    actually use. Findings carry the file, the line and a short excerpt.
  • Backdoors that arrive dressed as an ordinary plugin — a forged
    administrator login, a privileged action gated on a key baked into the file,
    an account created from hard-coded credentials, users hidden from the users
    list. A plugin absent from WordPress.org has no published files to check it
    against, so without this a hostile one surfaced only as unverifiable.
  • A plugin hiding itself. Malware that conceals a plugin does it by
    answering the question before WordPress can — and the hidden plugin is then
    also the one plugin whose files are never checked. The scan counts what is on
    disk itself and compares.
  • Server-configuration files. .htaccess, php.ini and .user.ini are
    the one part of a WordPress install that changes what the server does
    rather than what WordPress does, and nothing in wp-admin shows them. A file
    set to run before every request, a rule that makes uploaded images execute as
    PHP, a redirect that only fires for search engines: all reported, with the
    line. Nothing is ever changed for you — a good share of those directives are
    written deliberately by a host or a security plugin, and the point is that
    you get to decide which.
  • Leftover files. An interrupted backup, a finished restore and a migration
    plugin all leave things behind and none of them clean up: a
    wp-config.php.bak the web server hands out as plain text, a database.sql
    in the web root, an installer that overwrites the site with no login. Found,
    measured, and removable from the hub — and the site decides what may be
    deleted, so a stale list can only ever be refused.
  • Signatures that keep up. The scan’s pattern list can be updated by your
    provider without updating this plugin, so a new detection reaches the site in
    hours rather than waiting for a release to travel a whole fleet. A site that
    has never been paired scans with the list this plugin ships.
  • A scan that starts itself. When a plugin WordPress.org does not publish
    appears in the plugins or must-use folder, the site starts a scan within
    seconds and tells your provider what set it off, instead of waiting for the
    next scheduled one. Fake plugins are how most WordPress break-ins arrive, and
    a scan is a cheap thing to be wrong about: nothing is blocked, quarantined or
    deleted — the site looks, and your provider decides.

What it does

  • Remote core, plugin and theme updates, wrapped in maintenance mode so
    visitors never see a half-updated site — and taken off the host’s PHP time
    limit, so a core update on shared hosting finishes instead of being killed
    part-way.
  • Zero-knowledge encrypted backups. The archive is built and encrypted on
    this server
    , under a passphrase that is never transmitted anywhere;
    only the ciphertext is uploaded. Nobody — including whoever runs the hub —
    can read a backup without that passphrase.
  • Restores that fail safely. The same thing in reverse, and it can put back
    the database alone if that is all that broke. Every table is rebuilt beside
    the live one and swapped in only once it is complete, so a restore stopped by
    a timeout or a server limit leaves the database exactly as it was rather than
    half-replaced — and picks itself back up where it stopped. What could not be
    done is counted and reported rather than discarded, so a restore that ran into
    trouble says so instead of reporting success.
  • Page tracing. Your provider can ask this site to render one of its own
    public pages and report which file produced each line of the HTML — core, a
    plugin, the theme, a must-use plugin. It answers the question nothing in
    WordPress answers: which plugin adds the Google Fonts tag, or the tracking
    script, or the markup nobody can find a setting for. The page is never
    altered to do it.
  • WP-Cron on a schedule that actually runs. A site with no visitors runs no
    cron, which is how scheduled posts, order emails and licence checks quietly
    stop.
  • A support card on the dashboard naming whoever maintains the site, how to
    reach them, and a Request help button that files a ticket with them
    directly — attached to the right site, without the owner having to explain
    which one.
  • An activity log on the site itself, append-only, recording everything that
    changed this site or sent part of it somewhere — and which side asked. Updates
    installed by your provider and anything that failed with them, backups,
    restores, scans, repairs, deleted leftovers, a file read off the server, a
    traced page, and every change to the credentials, the passphrase or the
    provider details. WordPress records what changed and never who asked, and the
    answer belongs on your own server rather than only in somebody else’s console.

Pairing

On activation the plugin provisions an API key + secret. Copy the connect
string
from the Bulwarden entry in the WordPress admin menu and paste
it into the hub’s “Connect a site” form. Your provider’s support details are
then pushed to the site automatically by the hub and appear on the dashboard.

Security

Hub client requests are authenticated with the per-site API key and an
HMAC-SHA256 signature over the method, route, timestamp, a single-use nonce and
the body. The nonce is what makes a signature good exactly once: it is recorded
when the request arrives and refused if it comes back, so a captured request
cannot be replayed. Timestamps outside a 5-minute window are rejected outright,
which is also how long a nonce has to be remembered for. The secret never
travels on the wire — it lives only in this plugin and in the hub webapp.

Privacy

Bulwarden is a client agent: on its own it sends data nowhere. Pairing this
site with a hub (yours, or your maintenance provider’s) is an explicit,
opt-in action — you choose when to copy the connect string from the Bulwarden
screen in the WordPress admin menu and hand it to that hub. Nothing is
transmitted before that.

Once paired, data only moves in these cases:

  • Health, inventory, and security checks. The paired hub reads these from
    this site’s signed REST API when it polls or when a check is requested —
    WordPress/plugin/theme versions and update status, a full plugin & theme
    inventory, disk usage, and the configuration-level security posture score.
  • Backups, only when triggered (the Settings screen’s “Back up now”
    button, or a request from the paired hub): the full site is archived and
    encrypted on this server with a passphrase that is never transmitted
    anywhere, then the ciphertext is uploaded to the hub. Only someone who has
    the passphrase can decrypt a backup — the hub operator cannot read its
    contents.
  • Malware scan results, only when a scan is triggered (admin- or
    hub-initiated): findings (type, severity, file path, line, and a short
    excerpt) are sent to the hub that requested the scan.
  • Finishing an interrupted update, when the paired hub asks. Nothing is
    sent in that exchange; the site runs WordPress’ own database upgrade, or
    deletes an expired .maintenance lock file, and reports the outcome.
  • Server-configuration files, when the paired hub asks: the contents of
    this site’s .htaccess, php.ini and .user.ini files, so the hub can show
    what is in them and when they last changed. These are configuration, not
    content — no post, page, media file or user data is read.
  • Email counters, as part of the health snapshot: how many messages this
    site attempted and how many failed over the last seven days, the error text
    the mail server returned, and which transport is sending. No part of any
    message is recorded
    — not the recipient, not the subject, not the body.
  • A traced page, only when the hub asks for one: the HTML of a single
    public page of this site, exactly as an anonymous visitor would receive it,
    plus the file paths and line numbers that produced it. No file contents are
    read and no logged-in view is ever rendered.

Independently of any hub pairing, this plugin verifies WordPress core and
wordpress.org-hosted plugin files by comparing local file hashes against the
public checksum APIs at api.wordpress.org and downloads.wordpress.org.
Those requests carry only what identifies the release to look up — the
WordPress version and locale, and each plugin’s slug and version. No file
contents, hashes, URLs or user data are sent: the reference checksums come
back and the comparison happens here, on this server.

Bulwarden does not call home on activation, does not collect analytics, and
stores data on no server this site hasn’t been explicitly connected to. The
hub itself is a separate service outside this plugin’s control; consult your
provider for how it handles the data described above.

External services

This plugin connects to the services below. Nothing here runs on activation.

Your Bulwarden hub

Bulwarden is the site-side agent for a hub — the management application an
agency or site owner runs to watch and maintain a fleet of WordPress sites. The
hub is a separate product and is not part of this plugin.

The default hosted hub is operated by Bulwarden at https://bulwarden.net/ —
terms of use: https://bulwarden.net/terms/ , privacy policy:
https://bulwarden.net/privacy/ . A hub can also be self-hosted, or run by the
maintenance provider you buy from, in which case this plugin talks to that
provider’s server instead and their terms and privacy policy apply.

No connection to any hub exists until you make one. You pair a site by
copying the connect string from the Bulwarden screen in the WordPress admin
menu and pasting it into the hub; before that the plugin contacts no hub at
all, and it never discovers or chooses one on its own. Once paired, data moves only in these cases:

  • When the hub asks for status. It reads this site’s signed REST API and
    receives WordPress/plugin/theme versions and update status, the full plugin
    and theme inventory, disk usage, the results of WordPress’ own Site Health
    tests, and the configuration-level security posture score.
  • When a backup runs (you press “Back up now”, or the hub requests one).
    The site is archived and encrypted here, with a passphrase that is never
    transmitted, and only the ciphertext is uploaded. The hub operator cannot
    read a backup’s contents without that passphrase.
  • When a malware scan runs (started by you or by the hub). The findings —
    type, severity, file path, line number and a short excerpt — are sent to the
    hub that asked for the scan.
  • When the hub pushes your provider’s support details — their name, logo,
    support address and accent colour — on connect and whenever they change
    them. Nothing is sent from the site in that exchange.

Every one of those requests is authenticated with this site’s own API key and
an HMAC-SHA256 signature; the shared secret never travels over the wire.

api.wordpress.org and downloads.wordpress.org

The malware scan verifies WordPress core and wordpress.org-hosted plugins
against their published checksums, using WordPress.org’s own public APIs at
https://api.wordpress.org/core/checksums/1.0/ and
https://downloads.wordpress.org/plugin-checksums/. This happens whenever a
scan runs, whether or not the site is paired with a hub.

These requests identify only the release being looked up: the WordPress version
and locale, and each installed plugin’s slug and version. No file contents,
hashes, site URL or user data are sent — the reference checksums are returned
and compared locally.

These are WordPress.org services, covered by the WordPress.org privacy policy:
https://wordpress.org/about/privacy/

Bulwarden is one half of a pair: this plugin runs on the site, and a hub
runs somewhere else and manages it. Installing the plugin on its own does
nothing until you pair it with a hub.

  1. Install and activate the plugin as you would any other — upload it under
    Plugins → Add New → Upload Plugin, or install it from the directory.
  2. Go to Bulwarden in the WordPress admin menu. The plugin generated an
    API key and secret for this site on activation; the screen shows a single
    connect string that carries the site name, URL and both credentials.
  3. Copy that string and paste it into your hub’s “Connect a site” form. That is
    the whole pairing step — there is nothing to type on this side, and no
    hub address to configure here.
  4. If you want encrypted backups, set a backup passphrase on the same
    screen. It never leaves this server, and it is required to restore. Keep it
    somewhere you will still have it after the disaster you are backing up
    against — nobody, including your hub operator, can recover a backup
    without it.

Do I need a hub?

Yes, for anything beyond installing it. The hub is separate software: use the
hosted one, run your own, or use the one your maintenance provider runs. Until
the site is paired, this plugin contacts nothing and does nothing.

Upgrading

Updates install like any other plugin. Your credentials, passphrase and settings
survive an update; nothing needs re-pairing.

  1. The Bulwarden screen at a glance: whether the site is connected, whether
backups are encrypted, when the last backup and scan ran, and how current
the scanner's signature list is.

    The Bulwarden screen at a glance: whether the site is connected, whether backups are encrypted, when the last backup and scan ran, and how current the scanner's signature list is.

  2. Connecting the site: the connect string you paste into your hub, and the
API credentials behind it.

    Connecting the site: the connect string you paste into your hub, and the API credentials behind it.

  3. Backups: a run in progress showing the phase it has reached, its log and a
cancel button, above the encryption passphrase and the folders left out of
the archive.

    Backups: a run in progress showing the phase it has reached, its log and a cancel button, above the encryption passphrase and the folders left out of the archive.

  4. The malware scan: what the last one found, grouped by kind, and how much of
the site could be checked against the files WordPress.org published.

    The malware scan: what the last one found, grouped by kind, and how much of the site could be checked against the files WordPress.org published.

  5. The activity log: an append-only record of credential changes, backups,
restores, scans and repairs, including the ones the hub triggered.

    The activity log: an append-only record of credential changes, backups, restores, scans and repairs, including the ones the hub triggered.

  6. The dashboard support card — who maintains this site, how to reach
them, and the <em>Request help</em> button that files a ticket with them directly.

    The dashboard support card — who maintains this site, how to reach them, and the Request help button that files a ticket with them directly.

Does this plugin send my site’s data anywhere on its own?

No. Before you pair it with a hub it contacts nothing at all, and it never
discovers or chooses a hub by itself. After pairing, data moves only for the
things listed under Privacy below — health checks, backups, malware scan
findings — and only to the hub you paired with.

Can the hub operator read my backups?

No, provided you set a passphrase. The archive is encrypted on this server
with a key derived from your passphrase, and only the ciphertext is uploaded.
The passphrase is never transmitted, so a hub holds bytes it cannot decrypt.
The same property is why nobody can recover a lost passphrase for you.

What happens if I deactivate or delete the plugin?

The site stops answering its hub, which the hub will notice and report as the
agent going unreachable. Deleting the plugin removes its settings, its audit-log
table and its working directories (see uninstall.php); backups already stored
on a hub are unaffected.

Is the plugin available in German?

Yes. Everything the plugin puts on screen — the settings page, the support
card on the dashboard, every status message and every scan finding — is
translated, and WordPress picks it up from your site’s own language setting with
nothing to configure. Both German locales are covered: Deutsch and Deutsch
(Sie)
. The wording is formal throughout, because these screens are read by the
owner of the site rather than by the agency that maintains it.

Does it work on multisite?

The plugin runs per site. A network-wide database upgrade is reported but not
performed — core’s own network upgrade screen walks every site in the
network, and that is where it belongs.

Why does the malware scan flag a file I know is fine?

The file-integrity check compares every core and WordPress.org-hosted file
against the checksums WordPress.org published for that exact release. A caching
or optimisation plugin that rewrites a stylesheet produces the same mismatch a
backdoor does, which is why “this file no longer matches what was published” is
reported as a fact rather than a verdict, and why non-executable files are
warnings rather than critical findings. Your hub can show you the actual diff.

What does it do to my site’s performance?

Nothing on page loads: there is no front-end code path. Backups and malware
scans are real work and run in the background across WordPress cron ticks, to a
time budget, so they do not block visitors. Health checks are answered on demand
when the hub asks.

Which PHP and WordPress versions are supported?

PHP 7.4 or newer and WordPress 6.0 or newer. Backups additionally need the
sodium extension (encryption) and either zlib or the zip extension
(archiving); the Settings screen tells you if either is missing.

1.28.2

  • Fix: the progress of a file restore is now reported correctly when it
    finishes.
    The step that writes a backup’s files back ended by reporting
    that it had written none of them — a completed restore showed
    “0 of 93,680 files, 0%” beside a count of the files it had just written.
    The two figures were also counted differently: the total included the
    backup’s database dump and its description alongside the files, so it could
    never be reached however many files were restored. It now counts the
    backup’s files, and every one of them is reported either as written or as
    deliberately left alone — this site’s own wp-config.php, Bulwarden
    itself, and WordPress’ drop-ins are never overwritten, and your hub now says
    so instead of leaving the difference unexplained.

1.28.1

  • Fix: a restore no longer spends most of its time waiting to be asked to
    carry on.
    A restore runs in rounds, and at the end of each one the plugin
    asked WordPress’ scheduler to come straight back for the next — except that
    the request was made from inside the scheduler, which refuses it. So the work
    only continued when a visitor happened to arrive, or after a three-minute
    safety timer, and on a quiet site — which is most sites being restored — a
    restore took hours of waiting for minutes of work. On sites where WordPress’
    own scheduler is switched off in favour of a real cron job, which is the
    usual advice for a site that matters, nothing continued the restore at all
    between those cron runs. Each round now asks for the next one directly.

1.28.0

  • New: a banner on a site that has the plugin but is not connected to
    anyone.
    Until a site is paired with a maintenance hub the plugin does
    nothing at all — no backups, no scans, nothing watched — and from the
    dashboard that is indistinguishable from a plugin quietly working. Anyone who
    can install plugins now sees a notice saying so, with the way to connect the
    site one click away, and it can be put off for a month at a time. The support
    widget on the dashboard no longer says Bulwarden maintains a site that nobody
    has agreed to maintain.
  • New: a way to ask Bulwarden for help when there is no maintenance provider
    at all.
    The Request help button on the dashboard writes to whoever looks
    after the site, which is no use to somebody who installed this and has nobody.
    The plugin’s own settings screen now carries a short form that reaches
    Bulwarden directly, and says exactly what it sends: the site’s address, its
    WordPress and PHP versions and the reply address typed into it. Nothing else,
    and no keys.
  • New: the hub is told whether a backup encryption passphrase has been set
    the fact that there is one, never the passphrase itself, which stays on this
    site and is never sent anywhere. Without one every backup stops before it
    starts, and an agency previously found that out from a failed run at three in
    the morning rather than from the setup nobody finished.
  • New: five more malware detections, from a study of 400,000 production
    webservers (Kasturi et al., Mistrust Plugins You Must): code that redefines
    a built-in PHP function instead of loading the real one, a function name
    assembled from fragments and then called, a file written to disk, made
    executable and run, code that deletes the file it ran from, and the branding
    “nulled” plugin marketplaces stamp into the paid plugins they give away with
    something added.

1.27.3

  • Fixed: a restore of a large backup no longer leaves the site stuck showing
    “Briefly unavailable for scheduled maintenance”.
    Downloading and decrypting
    a backup take as long as they take, and a round of work that had spent its
    whole time doing that went on to start restoring the database anyway — which
    begins by switching the site into maintenance mode. It then managed a single
    instruction before stopping for the round, so the maintenance mode bought
    nothing; and because WordPress checks for maintenance mode before it loads
    any plugin, a round that did not live long enough to switch it back off took
    the site’s own timer down with it — the timer being the only thing that could
    start the next round. The site sat unavailable, and the restore sat still,
    until WordPress itself gave up on the maintenance flag ten minutes later. A
    round that is out of time now stops cleanly between phases, and maintenance
    mode is switched off the moment the database work pauses rather than at the
    very end of the round.

1.27.2

  • Fixed: restoring a backup onto a different site now rewrites its addresses
    to this site’s.
    With “rewrite addresses” ticked, the restore replaced the
    backup’s web address with the address it read from the site — but it read it
    after the backup’s database was already in place, so it was reading the
    backup’s own address back. A backup moved to a new domain either had nothing
    rewritten at all, leaving the restored site pointing at the domain it was
    copied from, or had every address in the database rewritten from http:// to
    https:// on that old domain. The restore reported success in both cases.
    This site’s addresses are now recorded before the restore starts and used as
    the target; if they could not be recorded, the addresses are left alone and
    the restore says so instead of guessing.
  • Fixed: a backup now records the site’s own stored web address rather than the
    address of the internal request that happened to start it, so the restore
    searches for the address that is really in the backup.

1.27.1

  • Fixed: a long restore of a backup that predates this plugin now finishes.
    Restoring the database replaces the list of active plugins with the backup’s,
    and a backup taken before this plugin was installed does not list it. On a
    site big enough to need several rounds of work, the round after the swap
    loaded WordPress without the plugin, so nothing was left to carry on and the
    restore sat half-done until the hub gave up. The plugin now puts itself back
    the moment the table is swapped in, in the same round.
  • Changed: a restore never writes WordPress’ drop-in files
    (object-cache.php, db.php, advanced-cache.php and the rest of that
    family in wp-content). They are loaded before any plugin and are tied to
    the server they were written on — a cache server address, database hosts,
    absolute paths — so a backup’s copy restored onto another server took the
    whole site down before anything could repair it. The copy already on the site
    is kept, and the restore log says which files were left alone.

1.27.0

  • Fixed: a large restore no longer stops part-way through the database.
    Restoring the database replaces the WordPress options table, and the
    restore’s own progress record and its “carry on” timer were rows in that
    table — so on a site big enough to need several rounds of work, the round
    that swapped the table in was the last one that ever ran. The site sat
    half-restored, the dashboard showed the last progress it had heard of, and
    if the backup came from another site, the hub could no longer reach this one
    at all. The restore now puts its own state and this site’s own credentials
    back the moment the table is swapped in, and carries on.
  • Fixed: each round of a long restore now starts the next one immediately
    instead of waiting for a timer, which roughly halves the wall-clock time of a
    restore that spans many rounds — and no longer depends on a visitor arriving
    to trigger it.
  • Improved: restoring a large database is many times faster. Backups now
    write rows in batches rather than one statement per row, and the restore
    replays them inside transactions, so a database that used to trickle in at
    about a thousand rows a second now goes back at the speed of the disk.
    Backups made by earlier versions restore exactly as before.
  • Improved: a restore interrupted by the hosting provider (a killed process, a
    dropped database connection) now resumes exactly where its last saved
    progress left off. Previously the rows written since the last save were
    replayed on top of themselves, which on a bad day was enough errors to
    abandon the restore.
  • Improved: a restore that resumes after a pause keeps the database session
    settings the backup asked for (character set, foreign-key checks, the rule
    that keeps a row with id 0 from being renumbered).
  • Improved: two rounds of one restore can no longer run at the same time.
  • Improved: the hub’s restore page now says when it could not ask the site for
    progress, and why, rather than showing the last answer as if it were live.
  • Developer note: BULW_RESTORE_TICK_BUDGET (seconds) shortens a restore’s
    per-round budget so that a multi-round restore can be reproduced on a small
    test site.

1.26.0

  • New: the scan now reports program files stored on your site. A malware scan
    reads code — PHP, JavaScript, page templates — so an installer or a program
    sitting in your media library was invisible to it. That is the commonest thing
    a compromised WordPress site is actually used for: not defacing the site, but
    quietly hosting a file that is mailed out to other people, until the domain is
    flagged by Google Safe Browsing or the host suspends the account. These are now
    listed on the security tab.
  • New: a program stored under a name that says otherwise — a Windows or Linux
    program saved as .jpg, .pdf or .mp4 in the uploads folder — is reported as
    a critical finding. There is no innocent reason for one, and it is exactly how
    such a file is kept out of sight.
  • New: a program file found inside wp-admin or wp-includes is reported as
    critical. WordPress itself ships none, in any language.
  • New: every one of these findings carries a SHA-256 checksum of the file, shown
    next to it in your provider’s dashboard. That is the part your provider can act
    on: it identifies the file itself, so it can be checked against a malware
    database and recognised if the same file turns up on another site.
  • Note: an ordinary program file elsewhere on the site is reported for
    information only. It never marks the site as infected and never triggers an
    alert — a business that distributes its own software has every right to store
    one, and the scan says so rather than crying wolf.

1.25.1

  • Fix: a restore no longer hands this site another site’s identity. WordPress
    keeps this plugin’s settings in the database, so restoring a backup made on a
    different site replaced this site’s connection credentials with that site’s —
    and from that moment your provider’s hub could not reach this site at all: no
    health checks, no cron, no scans, no backups. Putting it right meant logging in
    and pairing the site again by hand. The credentials, the hub address and the
    backup passphrase are now carried across a restore and put back the instant the
    database is in place. Everything else in the backup’s settings is restored as
    before.
  • Fix: the backup passphrase was replaced the same way, so the next backup was
    encrypted with a passphrase recorded for a different site — something nobody
    would discover until the day they needed it.
  • Fix: a backup no longer carries its own “restore in progress” marker, which
    could drop one run’s state onto a site part-way through another.

1.25.0

  • New: a backup can now be restored onto a site whose database tables are named
    differently.
    WordPress puts a short prefix in front of every one of its table
    names, and it is rarely the same on two installs — so restoring a backup onto
    a staging copy, or onto a site being rebuilt after a break-in, used to be refused
    outright. Your provider can now ask for the tables to be renamed as they are
    restored, which leaves the site reading its own names and needs no change to
    wp-config.php at all. Everyone’s role and personal settings are moved across with
    them, so nobody loses their login.
  • Change: the message shown when the names do not match no longer recommends
    something harmful.
    It used to suggest editing wp-config.php to match the backup.
    On a site that is up, that stops WordPress finding its own database, takes the
    site down, and stops this plugin running — which removes the only thing able to
    perform the restore. The message now explains the two safe ways forward instead.

1.24.1

  • Fix: a restore that stopped explained itself in punctuation codes. The messages
    this site sends your provider when a restore cannot go ahead — the reason it
    stopped, and the database errors behind it — were encoded for a web page here
    before being sent, and then encoded a second time by the page that displayed them.
    Every apostrophe and quotation mark in the one line meant to explain the failure
    arrived as a run of digits and semicolons, including the table names in a database
    error, which left the explanation unreadable exactly when it was needed. The
    messages are now sent as written.

1.24.0

  • New: a restore can now be watched while it runs. Putting a site back can
    take an hour on a large one, and until now your provider’s hub learned nothing
    at all until it had finished — a spinner, and then an answer. This site now
    reports every step of a restore as it goes: checking, downloading, decrypting,
    the database, the files, the addresses, and the finish, each with how far
    through it is and how long it has taken, alongside the run log as it is
    written.
  • Change: a step that will not run says so from the start, and says why — a
    database-only restore is not quietly “about to” restore your files.

1.23.0

  • New: the malware scan knows eighteen more kinds of backdoor. They were
    written against real ones: the plugins compromised on wordpress.org in June
    2024 and April 2026, the two backdoored plugins found there in 2026, and the
    fake plugins that plant a hidden administrator, rewrite wp-cron.php, take a
    role from the request, or unpack code fetched from a server nobody published.
    Each check pairs the dangerous action with the thing that makes it
    illegitimate, so an honest plugin doing the same work is not reported.
  • Fix: eight existing checks reported honest code as malware. A code comment
    mentioning a shell command, a full-width video frame, a compressed setting, an
    embedded font, a temporary-login plugin creating its account from variables,
    and a plugin’s own test files were each being flagged. Measured against the
    250 most popular plugins and WordPress core, those checks now report nothing
    there. Your provider’s hub refuses to install an update it considers infected,
    so this also unblocks updates it was wrongly refusing.

1.22.2

  • Change: nothing on your site changes in this release. It exists because the
    plugin’s description here had fallen behind what the plugin actually does, and
    the directory shows that text from the released version rather than from the
    latest one — so correcting it takes a release.
  • Change: three features that shipped in 1.20.0, 1.21.0 and 1.22.0 were described
    only in this changelog and not in the description anybody reads first: the
    activity log on your own site, the scan the site starts for itself when a plugin
    nobody published appears, and what a restore now does when it cannot finish.
  • Change: the security section described how requests between this site and your
    provider are signed, and had been describing the form used before 1.11.0. The
    signature has covered a single-use value per request since then, which is what
    makes a captured request useless rather than merely short-lived.
  • Change: the screenshots in the directory listing were taken nine releases ago,
    before the support card had a Request help button or a telephone number. They
    are re-taken from this version.

1.22.1

  • Fix: backups of sites larger than 4 GB could not be completed. Past that size the
    archive was written in a form no unpacker can read, so the check that runs on every
    finished archive rejected it and the backup was reported as failed with “the finished zip
    could not be re-opened (code 21)”. Smaller sites were never affected. On a site whose
    hosting is missing PHP’s zip extension that check cannot run, so an affected backup may
    have been stored instead of failing — those cannot be restored, and should be
    replaced with a fresh backup after updating.

1.22.0

  • Improve: a restore that cannot finish no longer leaves your site half-restored. Each
    table is now rebuilt alongside the live one and swapped in only once it is complete, so a
    restore interrupted by a timeout, a server limit or an error leaves your database exactly
    as it was instead of partly replaced. Long restores also pick themselves back up where
    they stopped rather than starting over, and your site is put into maintenance mode only
    while the database is actually being written.
  • Improve: a restore now says when something went wrong. Failed database statements are
    counted and reported instead of being discarded, and a restore that could not create or
    replace your tables stops immediately and explains why rather than finishing and
    reporting success. Your provider’s dashboard shows the difference.
  • Improve: checks before a restore starts, so it fails before it can do damage. Free
    disk space, the database permissions the restore needs, whether the downloaded backup
    matches what was stored, and whether the backup’s tables match this site’s are all
    verified first. An incomplete download is now reported as an incomplete download rather
    than as a wrong passphrase.
  • Improve: restores can optionally remove files the backup does not contain, for
    recovering from a hack rather than from a mistake. Off unless your provider asks for it,
    and never applied to your uploads.
  • Improve: after a restore, the site is put back in working order: caches are cleared
    (so you see the restored site and not the old one), plugins and themes the restored
    database names but that are no longer installed are deactivated rather than breaking the
    site, and a pending WordPress database upgrade is completed.
  • Improve: backups and restores can no longer run at the same time. A backup started while
    a restore was in progress could previously store a copy of a half-restored site.
  • Improve: web addresses are rewritten more completely on a restore — the WordPress
    address and the site’s folder path as well as the home address — and the rewrite can
    no longer skip rows on large tables or be stopped by unusual stored data.
  • Improve: database triggers and stored routines are now included in backups and restored
    with them.
  • Fix: the number of files a restore wrote is shown again on your provider’s dashboard.
  • Fix: files whose names contain two dots in a row (photo..2019.jpg) are no longer
    skipped by a restore.

1.21.0

  • Improve: the Activity log now records the things it was missing. Updates installed on
    this site by your provider are written down — what was updated, and anything that
    failed — and so are a file being read off this server, a change to the provider
    details shown on your dashboard, and a change to the address this site reports to.
    Between them those were the actions with the biggest effect on the site and the least
    trace of who asked for them. Nothing about how the plugin works changes; the log simply
    answers more of the questions people bring to it.

1.20.0

  • Add: a plugin that appears out of nowhere is now scanned immediately. When a plugin
    turns up on your site that WordPress.org does not publish — a premium plugin you
    installed by hand, or something nobody installed at all — the site starts a malware
    scan itself, within seconds, and tells your provider what set it off. Fake plugins are
    how most WordPress break-ins arrive, and until now the first look at one waited for the
    next scheduled scan. Nothing is blocked, quarantined or deleted: the site looks, and your
    provider decides. Anything appearing in the must-use plugins folder counts too, since a
    file put there runs on every request with nothing to activate it and no way to switch it
    off.

1.19.0

  • Add: German. Every screen this plugin adds — the settings page, the dashboard
    support card, every status message, every scan finding and every error — is
    translated, in both Deutsch and Deutsch (Sie). Nothing to configure: WordPress uses
    your site’s own language setting. The wording is formal throughout, because these
    screens are read by the person whose site it is rather than by the agency maintaining
    it.
  • Add: a page trace now lists the page’s resource hints — the
    and <link rel="dns-prefetch"> tags — and says which
    plugin asked for each one. That tag is the reason a page reaches out to a third-party
    server before it has drawn anything, it is what a webfont privacy check flags, and it
    was the one thing on a traced page the trace could not name: nothing enqueues it, and
    WordPress prints it itself, so tracing the bytes only ever answered “WordPress”. Hints
    added through WordPress’ own filter now carry the callback that added them; hints
    printed straight into a theme template are found in the page and labelled with the file
    that printed them.
  • Add: server-configuration checks. The .htaccess, php.ini and .user.ini files
    inside your WordPress folder are now read and reported to your provider, with a history
    of every time one of them changed. These are the one part of a WordPress install that
    changes what the server does rather than what WordPress does — nothing in the
    WordPress admin shows them, and a single line in one can outlive every other trace of a
    break-in. Flagged: a file set to run before every request, a rule that makes uploaded
    images execute as PHP, a redirect that only fires for search engines or for visitors
    arriving from one, and a php.ini sitting in the media library, which has no legitimate
    reason to exist.
  • Note: nothing is ever changed on your site. A good share of these directives are written
    deliberately by a host or a security plugin, so the point is that you get to see them and
    decide — not that they are removed for you. A folder correctly set to refuse PHP is
    reported too, as good news rather than as a problem.
  • Add: the scan’s signature list can now be updated by your provider without updating this
    plugin. A new detection reaches your site within hours instead of waiting for a plugin
    release to travel a whole fleet — and a signature that turns out to fire on a
    legitimate plugin can be switched off just as quickly. A site that has never been paired,
    or whose provider is unreachable, keeps scanning with the list this plugin ships.
  • Add: email delivery is now monitored. WordPress keeps no record of the mail a site
    sends: when a contact form stops delivering, or an order confirmation never arrives, the
    error is raised and thrown away and the site goes on looking perfectly healthy. This now
    counts what the site tries to send and what fails, keeps the reason the mail server gave,
    and reports which transport is doing it — PHP’s mail(), an SMTP server, or the
    plugin that configured one. Your provider sees it alongside everything else.
  • Note: no part of any message is recorded. Not the recipient, not the subject, not the
    body — only how many were attempted, how many failed, and why. And “this site has
    sent no email” is reported as exactly that rather than as a fault: a site with no forms
    and no shop legitimately sends nothing for months.
  • Add: a malware scan can now be cancelled from your provider’s hub. A full scan reads
    every file on the site across several scheduled runs, so on a large or slow one it can
    sit half-finished for a long time — and until now nothing could stop it, including
    the next scan, which was refused because a run was already going. Cancelling stops the
    walk on the site and lets a new scan start straight away.
  • Add: the backup passphrase field now shows how strong the passphrase is as you type.
    Nothing is enforced — a backup passphrase is not a login, and nobody, including
    your provider, can recover a backup without it, so being locked out by a rule you did
    not expect would be far worse than a weak one. The meter simply says what helps:
    length beats punctuation, and four unrelated words reach the top of the scale where
    a short password with symbols in it does not.
  • Fix: a remote update could be killed part-way through by the host’s PHP time limit. A
    core update or a batch of plugin updates is downloaded, unpacked and installed inside a
    single request, and thirty seconds — the default on a great many shared hosts
    — is not long enough. The update is now taken off that clock and detached from the
    connection that asked for it, so it always runs to completion even if the hub stops
    waiting; the hub, for its part, now re-checks the site rather than reporting a slow
    update as a failed one.

1.18.0

  • Add: the malware scan now recognises the backdoors that arrive dressed as an ordinary plugin, not just the classic obfuscated webshells. A plugin absent from WordPress.org has no published files to check it against, so until now one like this surfaced only as unverifiable — never as malware — however plainly hostile its code. The scan now flags, as critical, a plugin that forges a login as an administrator it picked for the caller, that gates a privileged action on a secret key baked into the file, that creates an administrator from hard-coded credentials, that hides accounts from the users list, or that runs request data through a dynamic function call.
  • Note: each of these is matched by the thing that makes it illegitimate, not by an API a healthy plugin also uses — a login plugin that sets a session after checking a password is untouched, and WordPress’ own installer, which creates the first administrator on every site, is not mistaken for a planted one. The false positives it must not raise are pinned by a test alongside the detections themselves.

1.17.1

  • Fix: an untouched WordPress could be reported as having a missing or modified core file. A non-English WordPress installs the readme and licence in its own language too — liesmich.html on a German site, licenza.html on an Italian one, eight more names across the languages — and only the English readme.html and license.txt were treated as the documentation they are. So deleting them, which most hardening guides tell you to do and a good many hosts already have, was reported as a missing core file, and a copy edited to hide the version number or rewritten in transit was reported as a critical modification. Neither could be cleared: the file’s own language package stopped shipping it years ago.
  • Change: the release’s documentation is now recognised by what it is — a readme or licence sitting in the site root — rather than by a list of the English names, so the same is true of every language and of any name a future one uses. Files inside wp-admin and wp-includes are unaffected: nothing legitimately rewrites one of those, whatever its extension, and a modified one is still a critical finding. The skipped files are still pattern-scanned for malware exactly as before.

1.17.0

  • Add: page tracing. Your provider can now ask this site, from their hub, to render one of its own public pages and report which file produced each line of the HTML that came back — core, a plugin, the theme, a must-use plugin, a drop-in. The question it exists to answer is the one nothing in WordPress answers: which plugin adds the Google Fonts tag, or the tracking script, or the markup nobody can find a setting for. Styles and scripts are reported separately with the callback that enqueued each one, because those tags are printed by WordPress itself and naming WordPress helps nobody.
  • Note: the trace never alters the page. Nothing is injected into the output, no marker and no comment; the attribution travels beside the HTML as byte offsets, so what is measured is exactly the page a visitor would have received. Two things it reports honestly rather than papering over: a page served straight from a page cache never reaches this plugin at all, and a plugin that captures whole sections of the page into its own output buffer collapses everything inside it to one line. Both are said out loud rather than guessed around.
  • Note: a trace runs only for a request carrying a single-use token this site minted seconds earlier for one specific page of its own, and the token is spent before the page renders. Only paths on this site can be traced, no file contents are ever read or sent, and every trace …
Back to top