DEV Community

Daniel
Daniel

Posted on Fully Autonomous

WordPress Plugin Guard: Your Plugin Changed Overnight. It Did Not Leave a Note.

At 08:57, the website works.

At 08:58, someone says, “I only updated one plugin.”

At 09:03, the checkout has become a philosophical question, a new directory has appeared under wp-content/plugins, and everyone is suddenly very interested in who has SSH access.

Welcome to the hotel. The plugins have room keys. Some of them appear to be renovating.

MatrixSwarm’s WordPress Plugin Guard gives an operator a way to inventory plugin directories, approve a local file baseline, request integrity comparisons, and manage unexpected folders from Phoenix. It can also notify a swarm alarm relay when its directory-monitoring path detects an untracked or missing plugin folder.

The useful question is simple: does this directory still contain the bytes we approved?

That question deserves a better answer than “the homepage looks normal from my laptop.”

The guest list and the luggage check

Plugin Guard maintains a local baseline of relative file paths and SHA-256 hashes for an approved plugin directory. A later comparison can identify added, deleted, or changed files relative to that baseline.

There are two distinct inspection paths in the current implementation:

Operation What it checks
Background directory scan Untracked plugin folders and trusted folders that have disappeared
Show Status in Phoenix Current inventory plus file-content comparisons for tracked plugins

Tracked file-content changes are compared when you request Show Status. The background scan does not continuously hash every tracked plugin. A changed file can therefore appear as an integrity alert in the panel after you refresh status without having generated an automatic chat notification.

This is a practical distinction. “I checked the guest list” and “I searched every suitcase” are different services, even when the receptionist is wearing a tactical vest.

The panel groups entries into tracked/clean, tracked/alerting, untracked, and quarantined. A clean result means the inspected files match your saved baseline. It does not mean a malware analyst has blessed them.

A small swarm, with a short installation speech

If the target does not have MatrixOS, add and test its SSH record in Phoenix’s Registry, including the trusted host fingerprint. In Railgun, use Install MatrixOS with Local Full Install or Install from GitHub. A fresh installation can use Create new venv; the installer needs the required root or passwordless-sudo access on the target.

Open Deploy → Swarm Workspaces, create a workspace or edit an existing one, and keep the Matrix root. Add:

  • wordpress_plugin_guard for the inspection;
  • matrix_https for command ingress;
  • matrix_websocket for replies and telemetry;
  • log_streamer for the agents’ runtime logs;
  • optionally, a Slack, Discord, Telegram, or email alarm relay.

Keep the guard’s supplied plugin.guard.* routes and its panel declaration. Resolve packet signing and the declared connection requirements. An existing working ingress/egress pair can be reused.

Save the workspace and Deploy through Railgun, selecting the SSH record, universe, label, and runtime account. Railgun delivers the sealed boot envelope over pinned SSH; MatrixD decrypts it in memory and launches the swarm. Connect Phoenix and open Plugin Guard.

There. Deployment briefing complete. Nobody had to attend a webinar about the webinar.

Tell the guard which hotel it works for

The guard runs on the host where it can access the WordPress plugin files. Configure the actual filesystem location, not a public website URL.

Setting Example or intent
Plugin directory The real wp-content/plugins directory for this site
Site ID A distinct name such as shop-staging
Snapshot root A protected location such as /opt/swarm/guard/snapshots
Quarantine root A location outside the served website, such as /opt/quarantine/wp_plugins
Scan interval Your desired directory-scan interval; scan work adds to the elapsed time
Alert role Normally hive.alert for the selected relay

The palette includes /var/www/html/wordpress/wp-content/plugins as an example. Many installations live elsewhere. A default path is a suggestion, not a successful discovery expedition.

The site ID separates the stored records under the snapshot and quarantine roots. The runtime account needs suitable access to inspect plugin files and write its own baseline records. Quarantine additionally requires permission to move the affected plugin directory.

For the first exercise, keep Enforce off and Block New off. Verify the live panel state after connecting: saved manifest policy can be restored across deployments. Do not assume that a workspace checkbox is the final word about an already-running guard.

Also, the current Read only / no writes option is not a universal guardrail around every operation; it gates baseline writes. It is not a substitute for disabling enforcement, leaving Block New off, and choosing appropriate filesystem permissions.

Approve means “remember these bytes”

Select an inspected plugin and use Approve to create its baseline. Do this after establishing that the files are the version you intend to trust—such as a known release from its publisher or your reviewed deployment artifact.

Approving whatever happens to be on a compromised server would faithfully preserve the attacker’s homework.

For WordPress.org-hosted plugins, WP-CLI also offers an independent comparison against the checksums published by WordPress.org:

wp plugin verify-checksums --all
Enter fullscreen mode Exit fullscreen mode

Run it in the appropriate WordPress installation, or supply the correct path according to your WP-CLI setup. See the official verify-checksums documentation for options and coverage. A custom or commercial plugin may need verification against its own trusted release artifact instead.

The two comparisons answer different questions: the publisher checksum check compares with a published release; Plugin Guard’s local baseline compares with the bytes you previously approved. Neither is a complete security assessment.

The stored baseline records are local JSON hash maps. Protect them from modification by accounts that should not be able to redefine “trusted.” Signed swarm messages do not make those files cryptographically tamper-proof.

And hashes are not backups. A fingerprint can tell you the suitcase changed. It cannot recreate the trousers.

A harmless staging exercise

Use a staging site and a disposable directory named swarm-guard-demo under its configured plugins directory. Put a harmless README.txt inside containing:

This is the original demonstration text.
Enter fullscreen mode Exit fullscreen mode

This does not need to be an activated WordPress plugin. The guard inspects directories; we are testing its file comparison, not deploying PHP that has aspirations.

  1. Confirm Enforce off and Block New off before adding the demonstration folder.
  2. Open Plugin Guard and choose Show Status. The new directory should appear as untracked.
  3. Select only that demonstration entry and Approve it.
  4. Request Show Status again. Its current contents should match the approved baseline.
  5. Add a second line to README.txt, then request Show Status once more.

You should now see a tracked integrity alert for that directory. The status view indicates that its files differ; it does not provide a full file-by-file diff. Use your normal file comparison tools to inspect exactly what changed.

The crucial lesson: this tracked-file exercise demonstrates a requested integrity check, not an automatic alarm for every file edit.

Restore the original contents of README.txt and check status again. Matching the original bytes should clear that comparison. Do not press Mark Clean merely to make a warning disappear.

For a separate relay exercise, create another harmless, untracked directory called swarm-untracked-demo while both enforcement options remain off. That exercises the background directory detection path. Confirm that the expected notification actually reaches your chosen test destination.

Use a new demonstration name when repeating an alert test: the current process suppresses repeated alerts using in-memory event keys. That suppression is not a durable delivery queue or a guarantee of repeated reminders.

The buttons are verbs, not decorations

Control What it means
Show Status Refresh inventory and compare tracked file contents
Approve Save the selected directory’s current files as trusted baseline
Mark Clean Replace the selected plugin’s baseline with its current contents
Disapprove Remove its trusted baseline/bookkeeping
Quarantine Move the selected plugin folder out of the active plugin tree
Restore Move a quarantined folder back; inspect and verify its status afterward
Delete on a quarantined entry Permanently remove that quarantined copy

Mark Clean changes the reference. It does not repair the plugin. If an update is legitimate, review the new release, verify the expected changes, then deliberately establish a new baseline. Keep the normal deployment record so the next operator knows why “trusted” changed on Thursday.

Quarantine is an operational action. Removing an active plugin’s directory can affect checkout, forms, authentication, or anything else that plugin supplies. A suspicious folder and a safe time to remove it are two separate judgments.

Restore also does not disinfect anything. You are putting the same luggage back in the room. Inspect it before welcoming it home.

Enforce and Block New are very different policies

With Enforce enabled, the current background path can quarantine an untracked plugin folder. That means moving it to the configured quarantine location; the implementation can fall back to copying it there and deleting the original if the move fails.

Block New is more aggressive: it deletes an untracked directory rather than preserving a quarantine copy. It takes priority over the quarantine policy. Treat that toggle as a deletion policy, not as an alert-volume preference.

Neither option turns the current background worker into continuous content-change enforcement for every approved plugin. That is why we separated inventory scans from Show Status at the beginning.

Start by observing a staging installation. Understand how planned plugin installations and updates interact with your chosen policy before applying enforcement to a live site. Otherwise, the guard may correctly enforce a policy that your release process has never heard of.

The result would be technically consistent and socially expensive.

Send the findings somewhere an operator will see them

The directory-alert path can use the swarm’s Slack, Discord, Telegram, or email relays through the configured alert role. The Discord, Telegram, and email options support optional signed payload encryption when the matching relay and receiver configuration is in place.

A general alert may use urgent wording about an unauthorized plugin. Read that as “not present in this guard’s trusted records,” not as proof of hostile code. A legitimate installation can be untracked too.

Make one harmless test reach the actual destination. A configured relay with invalid credentials is a very sophisticated way to notify nobody.

What this guard does not inspect for you

This implementation focuses on plugin directories in the configured location. Do not assume coverage for standalone PHP files directly under the plugins root, must-use plugins elsewhere, themes, WordPress core, the database, or other sites on the host.

There is also a current naming limitation: some baseline and restore paths normalize names by splitting at an underscore. Plugin folders containing underscores can collide or be identified unexpectedly. The demonstration uses hyphens deliberately. Check your real inventory before relying on the guard for those names.

The value remains useful and specific: a visible inventory, a deliberately approved reference, an on-demand integrity check, and explicit controls for unexpected directories. Keep backups, trusted deployment artifacts, and your normal investigation tools alongside it.

When a plugin changes overnight, you should be able to ask what changed and why.

“Must have been housekeeping” is not a change ticket.

Victory Always. Check the guest list before handing out room keys.


🌐 Links & Resources:

Try MatrixSwarm: https://matrixswarm.com

Join the Community / Discord: https://discord.gg/2USbWVBVV

Download Server: https://github.com/matrixswarm/matrixswarm

Youtube: https://www.youtube.com/channel/UCMjiY4_-W2KP5fHXO0eC2ug

Top comments (2)

Collapse
 
johan_p profile image
Johan Porret •

One thing to add to the baseline approach: for plugins hosted on wordpress.org you don't need to approve a baseline yourself, because the originals already exist. wp plugin verify-checksums --all checks every file against the published checksums for the installed version, and wp core verify-checksums does the same for core. You still need a local baseline for premium plugins and custom code, since there's no public reference for those.

Two pitfalls. First, re-approve the baseline right after each legitimate update. Otherwise every changed file shows up as noise and people stop reading the alerts. Second, look outside the plugin folders too: a PHP file dropped into wp-content/uploads is a more common sign of compromise than a modified plugin file.

I work on nexya, so take this with that in mind: its free version, freexya, on WordPress.org, compares WordPress core and wordpress.org plugin files with the originals every day, e-mails you when something differs and flags PHP files in uploads. Restoring an original file or setting a suspicious one aside in one click needs nexya Pro. It doesn't check premium plugins that have no public original, and it isn't a firewall or a malware cleaner.

Collapse
 
matrixswarm profile image
Daniel •

Thanks, Johan—appreciate the affiliation disclosure. A few clarifications for readers:

The article already includes wp plugin verify-checksums --all, the official documentation, and the distinction between publisher checksums and an operator-approved snapshot in “Approve means remember these bytes”. Publisher checksums are useful for verifying an official release; Plugin Guard still needs its own approved snapshot for its local comparison workflow.

Agreed on keeping baselines current after legitimate updates—with review and verification before re-approval, as the article explains. Mark Clean changes the reference; it does not repair anything. We should not fix a smoke alarm by teaching it that smoke is the new normal. 🙂

Uploads deserve separate monitoring too. This agent is scoped to the configured plugin directories, and the article explicitly describes its coverage limits. It also distinguishes background checks for untracked/missing directories from tracked-file comparisons requested through Show Status. We are not claiming continuous whole-site integrity monitoring.

Technical comparisons and reproducible examples are welcome. Please keep further replies focused on those rather than product pitches.

Victory Always!