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.
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.
.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.
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.
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.
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.
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
.datdropper plants the exact same backdoor, same MD5, that 15 of the original 2013 portfolio plugins ship inline. That ties@lulub5592to@siteguarding. - The captcha plugin hardcodes
image-optimizer-xas its forced-install target. That ties@lulub5592to@dalielsam. - The image optimizer’s
cmsplughub.comruns on SiteGuarding’s own nameservers and IP. That ties@dalielsamback 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.
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 namedplugin.dat.tmp. This file was executed, sending your website’s URL toapitest.siteguarding.comand installing a “Remote Management Tool” in the root directory as a file namedsiteguarding_tools.php. This tool allows connections from specific IPs belonging tositeguarding.comandsafetybis.comservers … 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.
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.
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.
@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.
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.
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_savefileandTask_showfile— write or read any file on the server.Task_includefile— write attacker PHP to a temp file andinclude()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.
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_tools.phpThe backdoor itself. Check the web root and any webanalyze/ folder.wp-math-captcha.datThe self-deleting dropper. Also .dat.tmp, tools.gzs and scan_sigs_db.dat across siblings.Any 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.core.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.logo_siteguarding.svg / sgAntiBot.phpCleartext SiteGuarding assets bundled by the SEOGuarding-branded suites (seo-pack, geo-traffic-control-and-redirect).3eddf6d18214d0d612809efd585a2471MD5 of the v1.7 backdoor shared across 15 portfolio plugins.siteguarding.comC2 and nameservers. Also apitest.siteguarding.com and siteguarding.com/ext/vitals/.cmsplughub.com / safetybis.comThe 2025 burner brand and the dissolved-company domain, both still live C2 surfaces on the same infrastructure.198.7.59.167Current control plane. rDNS resolves to server2.siteguarding.com. Also 198.7.59.150 / .168.185.72.157.169-172Legacy v1.7 operator IP range, hosted in Poland.17 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.
When working with an AI agent a single file can be all it takes to unravel something far larger.