Within Grok Bot I have a bot dedicated for WP Beacon. Every morning it reviews top plugin diffs on WP.org. After two weeks of running daily checks I got a ping. While I was working on something else it found malware sitting in trunk of a 40,000-install plugin.
The stable zip never moved, so auto-updates never shipped it to anyone. The plugins team closed the listing that afternoon and restored it the next morning after it was cleaned by the plugin author.
The bot already had a job.
I did not sit down that morning to hunt malware. This particular bot runs every morning. Updates WP Beacon’s website for plugin closures first, then yesterday’s plugin diffs, then last night’s Wordfence results. Not a full Beacon scan. Wordfence runs on my WP Beacon plugin mirror. Not on my laptop.

A full Beacon scan does too much for a morning pass. Closures, detection events, catalog diffs, and a corpus Wordfence run do not belong in the same conversation. You miss the thing that actually shipped yesterday.
So the morning job is four smaller items. Most days it has nothing to say.
Beacon dumps the diffs. The bot just reads them.
WP Beacon keeps a git copy of the top ~25,000 wordpress.org plugins. Every tagged version, real SVN authors and dates, public at plugin-repo.wpbeacon.io. Local copy is the source of truth. Production at wpbeacon.io is just a read-only push.
Overnight the box rebuilds whatever shipped. In the morning I do not need to talk to plugins.svn.wordpress.org. The history is already on disk.
A script called diff-day.sh walks every repo and dumps yesterday’s net change. One unified diff per plugin. PHP and JS stay, including minified. CSS, images, fonts, and translation files get dropped.
/srv/wporg/bin/diff-day.sh 2026-09-07
# → /srv/wporg/diffs/2026-09-07/_summary.tsv
# → /srv/wporg/diffs/2026-09-07/<slug>.diff
A typical day is a couple hundred plugins. Most of that is version bumps and asset-only releases. Beacon dumps the diffs. Grok Bot just reviews them. Same files I would read with /beacon-audit-diff by hand, except I was doing other work.
Wordfence is a separate pass on the same window. It wants file contents, not a unified diff. Overnight it scans the new PHP and JS from plugins at 10,000 installs and up. Fine for commodity malware. It will not catch the structural stuff Beacon already watches: update-channel hijacks, author flips, dormant backdoors.
The ping from Grok Bot.
After two weeks of quiet, this is what showed up while I was working.

Wordfence never saw it. Grok Bot did.
instawp-connect has 40,000 active installs. It was in scope for the overnight Wordfence pass. Still hits=0.
Not a slam on Wordfence. The payload lived in tailwind.config.js. It only runs if someone evaluates that config during a Node build. PHP page loads never touch it. Scanners built for webshells in PHP are looking at a different class of file.
Grok Bot does not need a signature. It reads what Beacon dumped. A 2.2 KB Tailwind config became 9.8 KB. The extra bytes looked up Ethereum transactions, XOR-decrypted a remote payload, and spawned detached Node loaders. That is not a Tailwind change. That is malware.
The payload was sitting in a Tailwind config.
InstaWP Connect is a staging and migration plugin. Normal Tailwind config at the top of the file. Then the module.exports block closes. Then a bunch of padding. Then minified Node.
The dropper sets an Ethereum address, queries public ETH RPCs and Blockscout for recent transactions, decodes the first eight bytes of tx.to as two IPv4s, fetches loaders at /0x/cls and /0x/ls, XOR-decrypts a body or an X-Payload-B64 header, and runs the recovered JavaScript in-process and via detached node -e. Campaign id in the sample: A8-6369-1. Same family as npm malware MAL-2026-12217 (tailwind-animate-plugin).
tailwind.config.jsPadded 2,200 → 9,794 bytes. Legitimate Tailwind, then the dropper.0xa322E5f3D311D3080e6f0121063e9aDC2490Ef1aOracle address. C2 IPs rotate via new transactions.193.247.144.38On-chain C2 observed 2026-09-08. Do not connect. IPs rotate./0x/cls /0x/lsDetached Node loaders fetched from the decoded IPs.X-Payload-B64XOR-decrypted remote payload, then eval / node -e.MAL-2026-12217Same Ethereum-oracle Node dropper as npm tailwind-animate-plugin. Campaign A8-6369-1.Trunk is not the release.
Trunk on wordpress.org is the staging copy of a plugin. The next release lives there while the author works. It is not what the directory serves. When you hit Install or Update you get a tagged zip. Trunk can be dirty, tagged, reverted, and dirty again without auto-updates moving an inch.
The same commit bumped the readme Stable tag toward 0.1.3.9. No tags/0.1.3.9 was ever published. downloads.wordpress.org kept serving 0.1.3.8.
If you installed from the directory, or you auto-update from the directory, you did not get this payload. The attacker padded trunk and never cut a tag. The plugins team said the same thing later: the attacker was not familiar with SVN. That complexity saved us this time.
Trunk was infected for about 23 hours. Risk is anyone who pulled trunk during that window, or ran Node against the plugin tree, including CI.
tailwind.config.js should be about 2.2 KB and must not contain the ETH oracle address, X-Payload-B64, /0x/cls, or child_process. Fast check: wp plugin verify-checksums instawp-connect.tailwind.config.js. Rotate secrets that were present on that laptop or CI runner. Reinstall from the current directory zip after verifying size. Directory and trunk builds checked on September 9 do not contain the dropper.Looked like a compromised publish path, not a hostile vendor.
Sole committer the whole time was @instawp. The inject and the revert both came from that identity. No new account showed up the day before. The same commit mixed a benign Sync DB fix under a version label that looked like an incident ticket. Next morning: “Remove accidental publish; restore stable 0.1.3.8.”
Sibling plugins on the same SVN account scanned clean. string-locator at 100,000 installs. instamigrate at 20. Same commit bit, no IOC.
MAL-2026-12217 is a known Ethereum-oracle Node campaign that pads Tailwind configs. That is not how a staging-plugin vendor backdoors their own product. It is how a compromised GitHub-to-SVN publish path gets used by someone who does not know that wordpress.org serves tags, not trunk, to the update API.
I had Grok Bot draft the email to the WordPress plugins team. I reviewed the email and hit send.
Plugins team closed it that afternoon.
I asked them to review trunk, consider a temporary close, and lock down the GitHub-to-SVN publish path until they were satisfied. I said it looked like a compromised publish path, not intentional malice. Trunk had been infected for roughly 23 hours.

Their read matched the audit. They closed the plugin. They believe the author would have done this much better than that. Luckily the attacker was not familiar with SVN. They also thanked Beacon for the catalog work, which I appreciate.
Vikas Singhal, who builds InstaWP, replied on my post a little after noon. “Yes already fixed for connect plugin.” That matches the early-morning revert.
They put it back the next morning.
September 9, wordpress.org fully reopened the listing. Downloads serve instawp-connect.0.1.3.8.zip again. Beacon rescanned. The served zip and SVN trunk tailwind.config.js (2,200 bytes) contain none of the dropper IOCs. Audit #53 is now marked cleaned.
Most days the bot has nothing to say. After two weeks it did. Now I get where Grok Bot fits into my AI mix of tools. Grok Bot is really good about handling scheduled repeat work. I’ve since started to have and more checks scheduled for Grok Bot to review. If anything needs my attention, I get a message from Grok Bot. This feels like the future.