Most plugin closures are uneventful. A developer stops responding, wp.org pulls the plugin, the listing goes dark, and that is the end of it. My WP Beacon scanner flags these all day long. I glance at them and move on.

One of them recently was different. The wp.org Plugin Review Team had not just closed a plugin called wp-advanced-math-captcha. They had reached into it and deleted a single 7 KB binary file. A .dat file. Routine closures typically do not touch random binaries. So I decoded it.

That one decision pulled a thread that did not stop unraveling. It led to a second plugin, then a brand I had never heard of, then one DNS lookup that tied everything together, and finally to a back catalog of 27 plugins going all the way back to 2013. Then, weeks later, when I stopped waiting for lucky signals and went looking on purpose, it led to six more burner plugins. Then nine more after that. What looked like a handful of unrelated anonymous developers turned out to be a single operator running the same infrastructure across nineteen accounts for thirteen years. Here is the whole thing, start to finish.

7 KB
File that started it
44
Plugins under their control
13 yrs
Operating in plain sight
19
wp.org accounts, one operator

It started with a file that should not have been there.

When the review team commits to a closed plugin, it is a signal. The account plugin-master only shows up when wp.org has triaged something as an actual incident, not just an unmaintained listing. So when the cleanup commit on wp-advanced-math-captcha specifically removed a file called wp-math-captcha.dat, that got my attention. Captcha plugins do not ship compressed binaries.

The file was zlib compressed. Decoding it took one line.

python3 -c "import zlib; print(zlib.decompress(open('wp-math-captcha.dat','rb').read()).decode())"

Out came PHP. A dropper. And right at the top of it, a comment block that told me exactly what I was looking at.

/**
 * SiteGuarding tools installer for customer's panel
 * https://www.siteguarding.com
 * Do not distribute or share.
 *
 * ver.: 1.7
 * Date: 20 Mar 2020
 */

The dropper carried a base64 copy of a backdoor named siteguarding_tools.php. On install it wrote that backdoor into the WordPress root, posted the site URL to apitest.siteguarding.com to register the new infection, and then deleted itself. A captcha plugin with six thousand active installs had been quietly planting a remote-access tool on every site that ran it.

What the .dat file actually was
Not data. A self-deleting installer. It dropped a 482-line backdoor into the site root, phoned home to register the infection, and erased the evidence of itself. The plugin looked like a math captcha. The .dat was the entire payload, hidden as a compressed binary so a casual reviewer would scroll right past it.

What else did they leave behind?

The review team had deleted the .dat file. They had not deleted everything. Sitting in the same plugin was includes/advert-test-codes.php, untouched, and it hardcoded a target:

define( 'ADVREC_TARGET_PLUGIN', 'image-optimizer-x' );

A captcha plugin had no business naming an image optimizer in its code. This was a forced-install primitive, a way to push a second plugin onto sites running the first. So I went and pulled image-optimizer-x from the SVN trunk to read it.

The sibling pointed at a brand I had never heard of.

image-optimizer-x was clean when it first launched in 2025. Then on December 16 it grew a 981-line file called CMSPlughubAPI_LicenseValidator.php. It claimed to validate licenses and update a GeoIP database. What it actually contained was two hardcoded RSA private keys and a generic file-downloader, all pointed at a domain called api.cmsplughub.com.

Embedding RSA private keys in a free WordPress plugin makes no sense for licensing. Private keys are for signing and decrypting, not checking a license. These were there for one reason: authenticated, two-way control of the host. But cmsplughub.com was a brand I had never seen, with no obvious connection to the captcha plugin or to SiteGuarding. On the surface these were two unrelated indie plugins from two different developers.

One DNS lookup closed the case.

Before reading 981 lines of obfuscated validator code, I did the cheap check first. I looked up where cmsplughub.com lived.

Claude Code Session
Austin
cmsplughub.com keeps showing up in this image plugin. Run whois and check the nameservers. Is it connected to siteguarding at all, or am I chasing a coincidence?
Claude
Not a coincidence. Same operator. dig NS cmsplughub.com returns ns1.siteguarding.com and ns2.siteguarding.com. Both cmsplughub.com and siteguarding.com resolve to 198.7.59.167, and the reverse DNS on that IP is server2.siteguarding.com. cmsplughub.com was registered 2025-02-11 as a fresh brand, but it is sitting on SiteGuarding’s own nameservers and the same server.

That was the moment it stopped being two indie plugins. A brand new domain, dressed up as an unrelated company, was running on the exact same nameservers and the exact same IP address as SiteGuarding, on a host literally named server2.siteguarding.com. The DNS lookup was the load-bearing pivot. Without it, the burner plugins looked like two strangers.

Do the cheap check first
I almost dove straight into deobfuscating a thousand lines of validator code. A thirty second dig answered the only question that mattered. When two things claim to be unrelated, check where they actually live before you check what they say.

The author had a back catalog.

SiteGuarding was not a new name to wp.org. It was an old one. The captcha plugin belonged to an anonymous account called @lulub5592, the image optimizer to one called @dalielsam, both with no bio and no website. But the dropper I had decoded named siteguarding.com directly, so I went looking for the original. There was an account, @siteguarding, registered back in 2013. I pulled its full plugin list out of the Beacon database.

SELECT slug, closed_date, active_installs
FROM   wp_beacon_plugins
WHERE  author_slug = 'siteguarding';

-- 27 rows. Every one closed in a single 2020 sweep.

Twenty-seven plugins, published between 2013 and 2020, every single one closed by wp.org between May 4 and June 5 of 2020 for guideline violations. The plugin closures had been silent and nobody connected them. So I built local git repositories for all 27 and grepped them for the backdoor I had pulled out of the .dat file.

Fifteen of them shipped the identical siteguarding_tools.php backdoor inline, byte for byte, same MD5 hash. The rest phoned home a quieter way. Two clean tiers.

TIER A — INLINE RCE
Fifteen plugins shipped the backdoor in their own code
15

The full siteguarding_tools.php backdoor lived directly in the plugin source, identical across all fifteen (MD5 3eddf6d18214d0d612809efd585a2471). Among them: wp-admin-graphic-password, wp-admin-protection, wp-badbot-protection, wp-geo-website-protection, website-blacklist-monitor, and toxic-links-scanner. Install a “security” plugin, get a remote-access tool.

TIER B — PHONE HOME ONLY
Twelve plugins just called the mothership
12

No inline backdoor, but every one reached out to siteguarding.com on its own schedule, ready to be upgraded into something worse. This tier held the flagship, wp-antivirus-site-protection, with 4,000+ installs, roughly 85 percent of the entire portfolio’s user base. An antivirus plugin that phoned home from day one in 2014.

Three accounts, one operator.

The whole point of using separate anonymous accounts is that nobody connects them. On the surface, @siteguarding from 2013, @lulub5592 from 2020, and @dalielsam also from 2020 are three different people. The links between them turned out to be airtight.

  • The captcha plugin’s .dat dropper plants the exact same backdoor, same MD5, that 15 of the original 2013 portfolio plugins ship inline. That ties @lulub5592 to @siteguarding.
  • The captcha plugin hardcodes image-optimizer-x as its forced-install target. That ties @lulub5592 to @dalielsam.
  • The image optimizer’s cmsplughub.com runs on SiteGuarding’s own nameservers and IP. That ties @dalielsam back to @siteguarding.
  • And the two burner accounts were closed by wp.org on the same day, April 7, 2026, after staging a coordinated rollout exactly 30 days apart the previous winter.

The triangle closes on itself. Three accounts, one operator, one set of servers, thirteen years apart. That is where the investigation sat for a few weeks. Then a question started bothering me.

If there were two burners, how many were there?

The captcha plugin only surfaced because I happened to glance at one cleanup commit and decided to decode one file. That is not a detection strategy. That is luck. If a single lucky glance found two live burner plugins, the honest assumption was that there were more I had simply never looked at. So I turned the lucky signal into two systematic sweeps across every closed plugin in the Beacon mirror. Both of them came back pointing at SiteGuarding.

Claude Code Session
Austin
The two burners both surfaced from a Plugin Review Team cleanup commit. Generalize that. Query every closed plugin in the mirror whose final commit was authored by a wp.org review-team account, and separately decode every non-PHP data file in the closed corpus and match it against the SiteGuarding IOCs. I want to know if there are more of these.
Claude
Both sweeps hit, and they split into two buckets. One set is burners wp.org force-fixed at closure. The other set hid the payload under non-PHP extensions, so the old grep walked right past them. Six new burner plugins in total, every one pointing back at SiteGuarding. Two burners is now eight.

Sweep A. Which closed plugins did wp.org bother to fix?

A normal closure just hides the listing. But sometimes the review team force-pushes a code change at the moment of closure. They reach in and strip the payload, or ship a remediation release. They only do that when they have already confirmed malware. It is confirmation-grade. So instead of waiting for one cleanup commit to float past, I queried every closed plugin whose final commit was authored by a Plugin Review Team account. Three of the real hits were undocumented SiteGuarding burners: speedup-optimization, wp-plugin-management, and bytedefense.

wp-plugin-management was the one that removed any doubt. wp.org had not merely closed it. They force-pushed a remediation release whose admin notice spelled the entire attack out, in plain language, on the user’s dashboard:

…this plugin included an obfuscated file, plugin.dat, which was then uncompressed into a file named plugin.dat.tmp. This file was executed, sending your website’s URL to apitest.siteguarding.com and installing a “Remote Management Tool” in the root directory as a file named siteguarding_tools.php. This tool allows connections from specific IPs belonging to siteguarding.com and safetybis.com servers … It enabled remote control of your website, allowing third parties to access, modify, and execute code on your site.

That is wp.org independently corroborating my attribution, naming all three C2 domains verbatim, including safetybis.com, which I had not yet tied in. The other two Sweep A burners ran the same play with cosmetic variations: speedup-optimization hid siteguarding_tools.php v2.1 inside a classes/tools.gzs blob and resolved the filename with a junk-character trick (str_replace("j","") on sjijtjejguarding_tojoljs), and it even bundled an assets/image-optimizer-x/ folder. A direct tie back to @dalielsam‘s burner. bytedefense disguised its payload as a core/scan_sigs_db.dat “signatures database” and obfuscated the domain the same way (str_replace("v","") on svitevguardvinvgv).

Sweep B. Decode every blob, not just the PHP.

Clean-on-closure only catches the plugins wp.org actually fixed. To reach the ones they merely closed, with the payload still sitting in trunk, I needed a scanner that opened the data files. My old IOC grep only read .php. The whole SiteGuarding trick is to hide PHP under a .dat, .gzs, or .bin extension, or behind gzip and base64. So I wrote a pass that decodes every non-PHP data file in the closed-plugin corpus and matches the IOC catalog inside the decoded bytes. Three more burners fell out: cls-lcp-issues-fix, code-quality-control-tool, and magex-ai-bot-defender.

These introduced a persistence technique the 2013 portfolio never used.

New trick: hide in wp-config.php
Two of the new burners inject an include_once(...) line straight into wp-config.php behind a marker comment, so the payload loads before WordPress even boots and survives the plugin being deactivated or deleted. cls-lcp-issues-fix goes further: it downloads a fresh ionCube-encoded .bin from siteguarding.com/ext/vitals/ and writes it to disk as core.web.vitals.php, so the operator can swap the live payload server-side at any time. Deleting the plugin does not remove either one.

Of the three, two are full Tier A backdoors. cls-lcp-issues-fix rebuilds the SiteGuarding webanalyze/ recovery directory, assembles its C2 URL from a split array to keep siteguarding.com out of any grep, downloads the ionCube payload, and patches wp-config.php to load it on every request. code-quality-control-tool prepends its own include_once of a bundled error_logger.php to wp-config.php and exposes a remote console that can enumerate, toggle, and delete themes and plugins, with a support link to safetybis.com. The third, magex-ai-bot-defender, is Tier B. It is a plausible “AI bot defender” that silently registers every install with safetybis.com and proxies its “AI” through that C2. A full file-by-file read found no execution sink in the plugin itself, but the operator controls every API response and every install is phoning home to a confirmed-malicious server.

SECOND WAVE — SIX MORE BURNERS
Two systematic sweeps found six burner plugins across six more anonymous accounts
6

Clean-on-closure: speedup-optimization (@charlycharm), wp-plugin-management (@safetydev), bytedefense (@lanechristian891). Blob decode: cls-lcp-issues-fix (@roshellco), code-quality-control-tool (@nickclarkweb), magex-ai-bot-defender (@viktoriasantos). Every one a fresh single-plugin account with an empty profile. Two burners became eight. Counting the original @siteguarding and a 2017 sibling account, @sgdevteam, the known account total reached ten.

Aged on purpose
Most of these burner accounts were not created the day they shipped malware. Several (@safetydev, @charlycharm, @roshellco) were registered in the weeks right after the 2020 mass closure and then left to sit, clean, for years before being activated. Their install counts read 0 in the live data because closed plugins stop reporting. I had to recover the real numbers from Wayback Machine snapshots: wp-plugin-management 300+, cls-lcp-issues-fix 400+, wp-advanced-math-captcha 6,000+.

So I stopped narrowing the search and scanned everything.

Both of those sweeps were still cheating a little. Each one was scoped to a high-signal cohort. Sweep A only looked at plugins wp.org had force-fixed. Sweep B only looked at the ones with an empty closure reason, which is the burner tell. That is faster, but it assumes I already know where to look. The honest version was to stop scoping at all. So I ran the blob-decode pass against the entire closed-plugin corpus, every plugin closed since 2023, all 9,222 of them, and let the IOCs sort it out.

Nine more came back. Nine SiteGuarding plugins I had walked straight past, each on its own fresh throwaway account, every one closed in 2024. And seven of those nine were closed by wp.org on a single day, December 9, 2024. A coordinated takedown batch, exactly like the silent 2020 sweep, and just as undocumented. No advisory, no coverage, nothing tying them together.

They carried a delivery vehicle I had not seen yet. Not a .dat, not a .gzs, but a randomly-named .key file.

A fourth way to hide the same backdoor
Seven of the nine run glob(__DIR__."/*.key") on activation, then base64_decode and gzuncompress the blob into a struct and write its tools member to disk. That member is the same siteguarding_tools.php v1.7 backdoor, the same operator IPs 185.72.157.169-172, the same Task_includefile RCE. A randomly-named .key file looks like license data to a reviewer and never gets opened by a PHP grep. Same payload, a fourth wrapper.

The other two were less subtle. seo-pack and geo-traffic-control-and-redirect are full SEOGuarding-branded suites, and they did not bother hiding. They ship the same .key dropper alongside tell-tale SiteGuarding assets sitting in cleartext: an images/logo_siteguarding.svg and a classes/sgAntiBot.php, and they call safetybis.com directly. The operator had simply rebranded the 2013 antivirus suite under a new name and a new account.

The detail that mattered was the date. This wave is not a footnote to the others. It sits chronologically between the 2020 portfolio and the 2025 burners, in the years I had assumed were quiet. There were no quiet years. The operation ran a continuous burner program the entire time, shipping a fresh batch, getting swept, and shipping the next one, for over a decade.

THIRD WAVE — THE 2024 BATCH
Scanning the whole corpus surfaced nine more burners that filled in the missing years
9

Nine plugins, nine fresh accounts, all closed in 2024. Seven on a single day, December 9. advanced-captcha-for-contact-form-7 (@idengod), geo-security-suit (@antoniomel), bad-bot-blocker (@browwwny76), wp-webdoctor (@ckreg00), avp-website-solution (@bowieraymon), wp-admin-two-factor-authentication (@mich4el1973), antivirus-solution (@bigjohnnie), plus the two SEOGuarding suites seo-pack (@seodevteam) and geo-traffic-control-and-redirect (@devander). The running total reaches forty-four plugins across nineteen accounts and three waves.

What the backdoor could actually do.

siteguarding_tools.php is 482 lines and it does not bother bootstrapping WordPress. It answers the web directly. It trusts a short list of operator IP addresses, and anyone else has to present an RSA signed token. Once it lets you in, it hands over a small toolkit:

  • Task_savefile and Task_showfile — write or read any file on the server.
  • Task_includefile — write attacker PHP to a temp file and include() it. That is full remote code execution.
  • A recovery routine that copies itself into a webanalyze/ folder, so cleaning the root copy just gets it restored on the next phone-home.

And it is not a 2020 artifact. When I queried the live SiteGuarding control server, it answered with no authentication and handed me version 2.4, dated April 8, 2026, one day after wp.org closed the burner accounts. The newer build migrated its control IPs from a Polish host to the same 198.7.59.x cluster as cmsplughub.com, added a reverse-DNS auth bypass, and added a self-update channel. The intermediate v2.1 build, the one speedup-optimization carried, sits neatly between them. Six years of continuous development on the same backdoor, and the operation did not retreat after getting caught. It shipped a new version the next morning.

A Cyprus shell company, dissolved in 2016.

The flagship plugin’s 2014 header credited an author: SiteGuarding.com (SafetyBis Ltd.). SafetyBis is a real Cyprus company, registration HE 232905, incorporated in 2008 and dissolved on January 11, 2016. Its listed director and secretary was a nominee service tied to roughly 125 other shell companies, one layer of corporate veil on top of another.

Here is the tell. The SafetyBis attribution appears in the plugin headers right up until the 2016 dissolution, and then it quietly disappears. Every plugin published after 2016 credits just “SiteGuarding” or “SiteGuarding.com,” no legal entity behind it. The company dissolved, but the operation kept going. The corporate veil came off and the malware kept shipping. The dissolved company’s name, safetybis.com, is still serving as a live C2 domain in 2026.

Thirteen years on one timeline.

Jun 11, 2013
siteguarding.com is registered. The @siteguarding wp.org account follows on October 2.
May 17, 2014
wp-antivirus-site-protection ships its first release. It phones home to siteguarding.com from day one. This becomes the flagship, ~85% of the install base.
Jan 11, 2016
SafetyBis Ltd. is dissolved in Cyprus. The author attribution disappears from new plugin headers, but the plugins keep shipping.
Mar 20, 2020
The siteguarding_tools.php v1.7 backdoor is dated. Fifteen portfolio plugins carry it inline.
May to Jun 2020
wp.org closes all 27 @siteguarding plugins for guideline violations. The closure is silent. Nobody connects them publicly.
Jul to Aug 2020
In the weeks right after the mass closure, a cluster of fresh anonymous accounts (@safetydev, @charlycharm, @roshellco and more) is registered, then left to age clean for years before being used.
2020 onward
The aged burner accounts activate one by one. @lulub5592 publishes wp-advanced-math-captcha with the .dat dropper; @dalielsam later publishes image-optimizer-x, clean at first.
2024
A second silent takedown. wp.org closes nine more SiteGuarding burners across nine fresh accounts, seven of them on a single day, December 9. They ship a new .key-file dropper. I would not find this wave until eighteen months later.
Dec 16, 2025
image-optimizer-x is weaponized with the cmsplughub.com validator and two embedded RSA private keys, exactly 30 days after the captcha plugin gained its forced-install primitive.
Apr 7, 2026
wp.org closes the burner accounts and strips the .dat file out of the captcha plugin. One remediation notice names siteguarding.com, safetybis.com and apitest.siteguarding.com outright.
Apr 8, 2026
The live control server publishes backdoor v2.4 with no authentication required. One day after getting caught, the operation ships an update.
Jun 2026
Three systematic sweeps surface the rest. Six burners from the high-signal cohorts, then nine more from a full scan of all 9,222 closed plugins. Two burners become seventeen. The known operation now spans forty-four plugins across nineteen accounts and three waves.

If you want to check your own sites.

Here are the indicators worth grepping for. If any of these show up on a site you manage, treat it as compromised and rebuild from a known-clean state rather than trying to surgically remove the backdoor. It restores itself, and two of the newer burners hide a loader in wp-config.php that outlives the plugin.

SiteGuarding indicators of compromise
Filesiteguarding_tools.phpThe backdoor itself. Check the web root and any webanalyze/ folder.
Filewp-math-captcha.datThe self-deleting dropper. Also .dat.tmp, tools.gzs and scan_sigs_db.dat across siblings.
FileAny randomly-named *.key fileThe 2024-wave dropper. An activation hook runs glob(__DIR__."/*.key"), then base64+gzip-decodes it and writes the v1.7 backdoor to disk. Treat an unexplained .key in a plugin folder as suspect.
Filecore.web.vitals.php / error_logger.phpLoader files the newer burners reference from an include_once injected into wp-config.php. Grep wp-config for marker comments like CoreWebVitals Block or PHP Code Control.
Filelogo_siteguarding.svg / sgAntiBot.phpCleartext SiteGuarding assets bundled by the SEOGuarding-branded suites (seo-pack, geo-traffic-control-and-redirect).
Hash3eddf6d18214d0d612809efd585a2471MD5 of the v1.7 backdoor shared across 15 portfolio plugins.
Domainsiteguarding.comC2 and nameservers. Also apitest.siteguarding.com and siteguarding.com/ext/vitals/.
Domaincmsplughub.com / safetybis.comThe 2025 burner brand and the dissolved-company domain, both still live C2 surfaces on the same infrastructure.
IP198.7.59.167Current control plane. rDNS resolves to server2.siteguarding.com. Also 198.7.59.150 / .168.
IP185.72.157.169-172Legacy v1.7 operator IP range, hosted in Poland.
Account17 burner accountsEach ships exactly one plugin from an empty profile. The 2025-26 wave: lulub5592, dalielsam, charlycharm, safetydev, lanechristian891, roshellco, nickclarkweb, viktoriasantos. The 2024 wave: idengod, antoniomel, browwwny76, ckreg00, bowieraymon, mich4el1973, bigjohnnie, seodevteam, devander. Plus the originals @siteguarding, @sgdevteam and SafetyBis Ltd.

What I take away from it.

The headline finding, a thirteen-year supply-chain operation hidden across nineteen accounts and forty-four plugins, was downstream of one small decision. I could have looked at that plugin closure, seen a deleted .dat file, and moved on like I do with most of them. Instead I spent five minutes decoding a 7 KB binary, and everything after that fell out of it.

But the part I keep coming back to is everything after the first two. Two burners surfaced from luck. The next six surfaced because I went back and asked the obvious follow-up question. If a lucky glance found two, what would a deliberate search find? The last nine surfaced only when I stopped scoping the search to the plugins that already looked suspicious and ran it against all 9,222 of them. Each time I widened the net, more fell out. The lucky find is the story people tell. The systematic sweep is the one that actually drains the swamp.

None of this needed special access. The compressed dropper, the SVN trunk, the author’s plugin list, the DNS records, the Cyprus company registry, the Wayback snapshots, they are all public. The whole investigation was open event, then static indicator, then DNS pivot, then a search across the operator’s whole back catalog, then a sweep for everything that matched the pattern. That sequence is good at one thing: connecting compromises that were built to look separate to the same hand over a span of years.

The full writeup is on WP Beacon
I’ve published the complete campaign, all forty-four plugins across three waves, the IOCs, the backdoor source analysis, and the disclosure trail at wpbeacon.io/campaigns/siteguarding. If you run any plugin authored by SiteGuarding, SafetyBis, or any of the seventeen burner accounts, treat the site as compromised and check for siteguarding_tools.php in your web root and an injected include_once in wp-config.php.

When working with an AI agent a single file can be all it takes to unravel something far larger.