Security incident on 31 July 2026: What Happened and What You Should Do

Quick Update 1:
We are sending emails to all the affected customers based on our logs, and our support team is helping our customers to clean up the sites. We want to make sure all the affected customers get notified and take necessary actions. If you want help with this matter, please email us at: [email protected]. Our team will reply and help you within minutes.
Quick Update 2:
Our team contacted the affected customers by email, LinkedIn, and phone, and helped them clean up their sites. Around 97.5% of all affected sites have now been cleaned up as of Aug 07, 2026. The remaining ones are mostly staging or local sites, and we are still trying to reach their owners.
On 31 July 2026, someone served changed versions of two of our plugins through a server of ours that should have been switched off a long time ago. If your site updated during a five hour window that day, it may have downloaded plugin files with code in them that we did not write.
We have emailed every customer whose site downloaded either plugin on 30 or 31 July. If you did not get an email from us, your site is very likely not involved. I am publishing this anyway, because you deserve to see the full story and not just the parts that fit in an email.
The short version
- Fluent Forms Pro 6.2.7 was tampered with. The current version is 6.2.10.
- Ninja Tables Pro 5.2.11 was tampered with. The current version is 5.2.14.
- The bad files were available for about five hours, between 14:00 and 19:00 UTC on 31 July 2026.
- We emailed 1,368 customers whose sites downloaded either plugin within 36 hours of the incident.
- Around ~295 customers downloaded the tampered version, but we emailed all the customers who downloaded before and after the timeframe.
- If you are on the clean versions and you have checked your site, you are fine.
What happened
Plugin updates do not come straight from one machine. Update requests hit a proxy, and the proxy decides which server behind it answers each endpoint.
A while back we moved our store and licensing off EDD (Easy Digital Downloads) and onto our own system. When we finished that migration, we left the old server running. We also left routing rules on the proxy that still sent a few endpoints to that old server. Both of those should have been removed when the migration was done. They were not.
Someone got into that old server. Because the proxy was still routing some update traffic there, they did not need to break into our current systems or trick anyone into downloading from a strange place. They changed the files that old server handed back, and the proxy passed them along to customer sites as a normal update.
That is how changed copies of Fluent Forms Pro 6.2.7 and Ninja Tables Pro 5.2.11 ended up on real sites. The files looked normal but had extra code added to them. Any site that updated while those routing rules were live got the changed files. This includes sites with auto-updates turned on, where nobody had to click anything. After the incident timeframe (Aug 01), the attackers may implant files on mu-plugins, other plugins, or install new plugins with infected files.
I want to be plain about this part. Our customers did nothing wrong and nothing unusual. They updated a plugin the way you are supposed to, and our own routing sent them to a server we had forgotten to switch off.
We noticed it the same day, stopped the update, removed the attacker’s access, and put out clean builds of both plugins.
Who is affected
Your site may be affected if either plugin updated to 6.2.7 (fluent forms pro) or 5.2.11 (ninja tables pro) during those five hours on 31 July 2026. Because of network caching, a small number of updates on 1 August may also have received the tampered files, so please check both days in your update history. Auto-updates count, and you did not have to do anything unusual for this to reach you.
Two things worth being clear about.
First, our download records show which sites requested a file and when. They do not show exactly which copy of the file each site received. So we cannot look at the time alone and tell you that you are safe. That is why we emailed everyone who downloaded on 30 or 31 July and not only the sites we could match to those five hours. Some people got an email even though their site is fine. We thought that was better than missing someone.
Second, if your site has already auto-updated to the clean version, the bad files are gone. That is good, but it does not undo anything that code may have done while it was running. Please still check your site.
How many sites this actually reached
We emailed 1,368 customers. The real number affected is smaller than that, and we want to be open about the difference.
As of 1 August, our own checks show around 295 customer accounts.
We could have emailed only those 295 people. We chose not to. Our records show which sites asked for a file and when, but not exactly which copy each one received, so drawing a tight line around “definitely affected” would have meant guessing at the edges. That is how somebody gets missed. Instead, we emailed every customer whose site downloaded either plugin on 30 or 31 July, which is 1,368 people. We also considered the network caching, so anyone can get the infected version even after a few hours closing down the server.
Most of them are fine, and their email will turn out to have been unnecessary. We think an unnecessary email is a much smaller problem than a missed one.
These numbers may move as we keep checking. If they do, I will update this post.
What we have done
- Removed the attacker’s access and changed every password and key involved
- Removed the leftover proxy routing rules, so no endpoint reaches that old server
- Shut down the old server for good
- Went through the rest of our routing rules and checked where every endpoint actually goes, in case anything else was left pointing somewhere it should not
- Replaced the bad files with clean builds and released them
- Emailed every customer whose site downloaded either plugin on 30 or 31 July.
- We are planning to add checksum-based updating across all of our plugins.
We are still going through the changed files and our server logs. If we find anything that changes the advice above, I will update this post and say what changed and when.
Timeline (UTC)
| When | What |
|---|---|
| 31 July, around 14:00 | The old server starts handing back changed plugin files, and the proxy passes them on |
| 31 July, around 19:00 | We spot it and take the update offline |
| 31 July, evening | Attacker access removed, credentials changed, proxy routing rules removed, old server shut down |
| 31 July, evening | Clean builds released: Fluent Forms Pro 6.2.10 and Ninja Tables Pro 5.2.14 |
| 1 August (06:00 UTC) | 1,368 affected customers emailed |
| 1 August | This post published |
| 1 August | Sent reminder email to all affected customers |
| 1 August | Our Team is helping all the customers who contacted with us |
| 2 August | Continue helping the customers to cleanup the sites |
| 2 August | Sent another reminder email to affected customers based on our logs. |
| 3 August | Helped customers to cleanup and sent another follow-up emails to affected customers |
| 4-6 August | Sending more emails to the affected customers and helping customers to clean up via Google Meet call. |
| 5-7 August | Reaching out to customers via LinkedIn, cell phone, secondary emails about the incident and helping them to cleanup their sites. |
| 7 August | Our team contacted the affected customers by email, LinkedIn, and phone, and helped them clean up their sites. Around 97.5% of all affected sites have now been cleaned up. The remaining ones are mostly staging or local sites, and we are still trying to reach their owners |
Getting help
If you think your site is affected and you want help cleaning it up, email contact_support [at] wpmanageninja.com and tell us. We will put you at the front of the queue. There is no cost for this and it does not count against your support limit. If you manage many sites, tell us how many and we will work through them with you.
What to check and do
You do not have to take our word for whether your site was affected. Every check
below can be run by you, on your own site, and every one looks for something
specific. If any of them hits, go to If any check hits at the bottom, then
email us — we will do the rest with you.
Work through them in order. Checks 1 and 2 need nothing but your WordPress
dashboard and an FTP client or file manager. Checks 3 and 4 need database
access (phpMyAdmin, or WP-CLI if your host provides it).
Check 1 — Your plugin versions
The tampered builds were served for about five hours on 31 July 2026. Clean
releases are out now. Open Plugins → Installed Plugins and compare:
| Plugin | Clean from | Update if you are below this |
|---|---|---|
| Fluent Forms Pro | 6.2.10 | ✔ |
| Ninja Tables Pro | 5.2.14 | ✔ |
Being on an older version does not mean you were compromised — only that you
were in the group that could have been. Checks 2 to 4 tell you whether anything
actually landed.
Please do not stop at updating. Installing the clean release removes the tampered code, but it does not finish the job. The tampered code leaves rows behind in your database that keep working after the plugin files are replaced. Check 3 is the one that finds those.
Check 2 — The file that should not be there
Each tampered build carried one extra PHP file, named to look like part of the
plugin’s own updater. Look for it under wp-content/plugins/:
| Plugin folder | File that should not exist |
|---|---|
fluentformpro/ | libs/class-license-sync.php |
ninja-tables-pro/ | app/Library/updater/NinjaTableDataSync.php |
Neither file exists in any legitimate release we have shipped. If one is there,
that site received a tampered build.
You can also confirm the tampering from the plugin’s own main file. In a
tampered Fluent Forms Pro, fluentformpro.php has this near the top, around
line 22 — it is not in any clean release:
require_once FLUENTFORMPRO_DIR_PATH . 'libs/class-license-sync.php';
If you have SSH access, one command settles both plugins at once. It searches
for the address the tampered code contacts:
grep -rl "apii.observer" wp-content/
Silence means nothing was found. Any file path in the output is a hit.
wp-content/mu-plugins/ — check this directory even if you have never used it. Anything in it loads automatically on every request and never appears in your plugin list. We have seen a small file here named db-repair- followed by hex characters, which grants administrator access with no password. This is the file that survives everything else. If your mu-plugins directory contains anything you did not put there, treat the site as compromised regardless of what the other checks say.
wp-content/uploads/ — look for .php files anywhere under uploads. There should be none. The ones we have seen have eight-character hexadecimal names.
Check 3 — Your database
The database query. This is the single most reliable check. Run it against your WordPress database (Adminer, phpMyAdmin, or WP-CLI). Change wp_ if your table prefix is different.
SELECT option_name FROM wp_options WHERE option_value LIKE '%apii.observer%';
Any row returned means the code ran on your site. It searches the value rather than the name, because the option names vary between versions but every version has to store the same address.
Via WP-CLI:
wp db query "SELECT option_name FROM wp_options WHERE option_value LIKE '%apii.observer%';"
The option names we have seen are _wp_update_meta_cache, _site_transient_update_meta, _wp_update_result_cache, _site_transient_update_result and _wp_update_pending_reg. They are named to look like WordPress core transients. Do not rely on this list — use the query.
The implant registers a REST namespace of wp-update/v1 and reports to
https://apii.observer/ingest with the key (campaign ID): e3462089a2defd78830c6bf8619d9411
Scheduled tasks. Look for wp_update_check_schedule and wp_license_verify_schedule, both running twice daily.
wp cron event list
Test the REST route. In a browser, visit:
https://yoursite.com/?rest_route=/wp-update/v1/check
A 404 is what you want. Any other response means the code is active.
Search your access logs. Look for requests containing confirm_admin_email with an unusually long wp_lang value (long 32-character random code, not a normal value like en_US), and for requests containing updatelink=. Search for those strings rather than for specific paths — the paths differ between installs, and on Bedrock or any install where WordPress core sits in a subdirectory, only the root-path forms will appear.
Check 4 — Signs someone acted on the access
Checks 1 to 3 tell you whether the door was open. This one tells you whether
anybody walked through it. Where the attacker’s server issued instructions, we
have seen it create administrators, write files, deactivate security plugins,
and read database credentials out of wp-config.php.
- Administrator accounts. Go to Users → All Users → Administrator and
look for any account you cannot place, particularly one registered on or
after 31 July 2026. With WP-CLI:
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
- Your access logs, for the back-door sign-in link. The tampered code plants
a URL that signs the caller straight in as your lowest-numbered
administrator, disguised as an ordinary WordPress email-confirmation link.
Search your logs for requests carryingaction=confirm_admin_emailwhere thewp_langvalue is an unusually long random code (32 characters). A normal login or confirmation link carries a short language value likeen_US, so seeingconfirm_admin_emailwithwp_langalone is not proof; some security plugins such as Wordfence produce that combination for real staff logins. The long random code inwp_langis the sign of the attacker. wp-content/mu-plugins/. Anything in this folder runs on every request
and cannot be switched off from the dashboard. Some hosts and security
plugins legitimately put files here — the question is whether you or your
host can account for each one, and whether its contents read like plugin code
or like an obfuscated blob.- A security plugin that switched itself off, or whose settings changed
without you touching them.
One caution about scanners. Please do run Wordfence, Patchstack or Sucuri
— but a clean scan is not by itself proof. When the tampered code creates an
administrator, it also adds that account to the list of admins your security
plugin already treats as known, specifically so the new-admin alert stays
quiet. Checks 2 and 3 are the ones that do not depend on the attacker
cooperating.
If any check hits
1. Clean up in this order
The order matters. The scheduled tasks recreate the database rows within hours. If you delete the database rows first, they come back and the site looks clean when it is not.
- Put the site into maintenance mode if you can, or take it offline.
- Delete the plugin. Delete the directory, do not deactivate. Your forms, entries, tables and settings live in the database and are not affected.
- Empty wp-content/mu-plugins of anything you did not put there. If you are not sure what belongs there, ask your developer or ask us.
- Delete any
.phpfiles under wp-content/uploads. - Delete the database rows, using the query above to find them.
- Clear the scheduled tasks:
wp cron event delete wp_update_check_scheduleandwp cron event delete wp_license_verify_schedule. - Re-run every check in section 1. Do not assume the deletions worked — confirm it by reading the state back.
- Run the checks again 24 hours later. If anything has returned, something is still running. Stop and contact us.
- Install a fresh copy of the plugin from your account at https://wpmanageninja.com/account/.
2. Rotate your credentials — after cleanup, not before
Anything on this site should be treated as known to whoever did this. They had administrator access. Rotating before you have finished cleaning just hands over the new credentials too, so do this last.
- Replace the salts and keys in
wp-config.php. Generate new ones at https://api.wordpress.org/secret-key/1.1/salt/. This signs everyone out, including them. - Reset every administrator password. Reset editor and author passwords too if the site holds anything sensitive.
- Revoke all application passwords, under Users → Profile.
- Rotate every credential stored in the site: SMTP details, payment gateway keys, any API keys in plugin settings, any tokens for connected services.
- Rotate database credentials and hosting or SFTP passwords if you can.
3. If you keep audit logs, read them carefully
Because this code signs in as an existing administrator rather than creating its own account, the activity appears in audit trails under a real person’s name. This does not mean that person did anything. Please do not treat a staff member as a suspect on the strength of a log entry.
4. Check your other sites
If other sites share the same server or hosting account, check them too. If you build or maintain sites for clients, check each one. If you manage many, email us and tell us how many — we will work through them with you.
5. If you cannot verify the site is clean
Restore from a backup taken before 31 July 2026, or rebuild the site and import the database after checking it against section 1. If you are not confident either way, email us before doing anything else. There is no cost for this help and it does not count against your support limit.
Indicators of compromise
The modified fluentformpro.php differs from the clean release by one require_once line. The implanted file carries a false plugin header: WP License Sync, author WordPress.org, version 2.3.0.
Dropped files — in wp-content/uploads/ and wp-content/mu-plugins/, 8-hex names, 296 and 220 bytes. Plus db-repair-*.php in mu-plugins, 476 bytes: a standalone passwordless admin bypass that works independently of the plugin and the database rows.
A separate set has been reported at 291 and 536 bytes with 12- and 16-hex names. It will not match signatures written for the main implant. Check both directories.
Network — C2 https://apii.observer/ingest
REST route — wp-update/v1, at /?rest_route=/wp-update/v1/check.
Authentication requests — 32-character token, 24-character key:
/wp-login.php?action=confirm_admin_email&wp_lang=TOKEN&redirect_to=KEY
/wp-admin/admin-ajax.php?action=confirm_admin_email&wp_lang=TOKEN&redirect_to=KEY
/wp-login.php?action=postpass&t=TOKEN&k=KEY
/?action=confirm_admin_email&wp_lang=TOKEN&redirect_to=KEY
/?updatelink=TOKEN&auth=KEY
On installs where WordPress core sits in a subdirectory (such as Bedrock), only the last two root-path forms will appear in logs. Search for confirm_admin_email with a long wp_lang value, and for updatelink=, rather than for exact paths.
Talk to us
contact_support [at] wpmanageninja.com
Write to us whether or not a check hit. If you are not sure how to read a
result, send us what you saw and we will tell you what it means. If a check did
hit, we will help you clean the site — we will go through the database rows
with you, confirm what was touched, and stay with it until the site is verified
clean.
This help is free and it does not count against any support limit. You did
nothing wrong here. Our own routing sent you to a server we should have
switched off, and cleaning up after that is our responsibility, not yours.
What I want to say
This was our fault. That old server should have been switched off when we finished migrating away from EDD, and the routing rules that still pointed at it should have gone with it. We left both in place, and someone used them to put code on your sites through a door with our name on it.
You trust us with code that runs on your site. That trust is the whole business. We let you down here, and I am sorry. We are fixing the things that made this possible, and I would rather tell you about it plainly than quietly move on.
If you have questions, write to me at contact_support [at] wpmanageninja.com.
Shahjahan Jewel
Founder, WPManageNinja LLC

I’ve been working with WordPress since 2010, building plugins and growing WPManageNinja into a global business with 120+ team members. Our products, including FluentCart, Fluent Forms, FluentCRM, and Ninja Tables, now power over 1.6 million websites.






Leave a Reply
You must be logged in to post a comment.