On 1 October 2026 a company's terms and conditions page started answering "No results found". So did its privacy policy, its cookie policy and two more. The pages were all still in WordPress, published and untouched. A plugin nobody had installed was running, and the Plugins screen did not list it.
This is how that site was put right, without the company's name. It was about ten minutes of work: six from the first look at the rewrite table to pages that loaded again, and a few more to remove the plugin, with no SSH and no hosting panel. The parts worth keeping are how it was found, the order things were removed in, and one mistake in that order.
What the owner saw
Five pages, all legal ones, all at the top level of the site: /terms-conditions/,
/privacy-policy/, /cookie-policy/ and two others. Each loaded the theme
and then the theme's "No results found" message. Every other page worked. So did everything with
a parent in its address.
That last detail is the clue. When only addresses with a single segment break, the pages are not the problem. The thing that decides which page an address means is.
One rule in the wrong place
WordPress keeps a table of rewrite rules: patterns on the left, what to load on the right. It tries them in stored order and stops at the first match. On this site, rule 18 of several hundred was this:
^([^/]+)/?$ => index.php?v_post=$matches[1]
Read it as: any address made of one segment is a v_post. WordPress's own rule for
pages sits much further down the table:
(.?.+?)(?:/([0-9]+))?/?$ => index.php?pagename=$matches[1]&page=$matches[2]
So /terms-conditions/ never reached the page rule. WordPress went looking for a
v_post called "terms-conditions", found none, and handed the theme an empty result.
Nothing was deleted and nothing was defaced. One line, placed early, was enough to take a
company's legal pages off its own site.
Nothing on the site's plugin list registers a post type called v_post. Which raised
the next question.
Fifteen plugins, fourteen on screen
The Plugins screen in wp-admin showed 14 active plugins. The active_plugins option
in the database, which is the list WordPress actually loads from, held 15. The fifteenth was
wp-core-util/wp-core-util.php.
There is no trick in how a plugin does this. The Plugins screen draws its list through a filter
named all_plugins, and any plugin may hook that filter. One that removes its own
row keeps running and stops being shown. Agency white-label tools use the same hook on purpose.
So does a plugin that would rather not be found.
The plugin list is what the site chooses to show you. The database is what it runs.
What it had set up
What could be read from the site while the plugin was still active:
- Two REST namespaces of its own,
wpu/v1andwpccpu/v1, with routes namedstatus,settings,posts,custom-links,updateandself-flush-rewrites. - Three rows in the options table:
_wpu_api_key,_wpu_access_mode, and a transient holding 185 address ranges belonging to Google's crawlers. - Its own status answer: version 1.1.6, no posts, access mode "disabled".
A list of crawler addresses is what a site needs in order to show search engines one page and
visitors another. A route called update is a way to change the plugin's code from
outside. Put together it reads as a doorway for search spam that had been installed and not yet
switched on. The broken legal pages were a side effect of the rule it added for pages it had not
created yet.
That reading comes from what the plugin left in the database and what it answered, not from its code. The code was never read, for a reason the next section owns up to.
The cleanup, in order
- The rule came out first. The rewrite table was read, the one rule removed, the table written back. All five pages returned at once. This was the urgent part and it took six minutes.
- The site owner switched on plugin management in Respira's settings. An AI agent cannot switch that on for itself; it is refused by design.
- The plugin was deactivated and deleted. Its two REST namespaces started answering 404.
- The site was scanned again. Core files matched WordPress's own checksums, there were no must-use plugins, and no administrator had been added in the past month.
Step 3 is the one to do differently. Deleting a plugin through WordPress runs the plugin's own uninstall code on the way out, and then the files are gone. On this site that code cleared the plugin's options, and the folder went with everything that could have said which domains it called and when each file was written. The site was clean afterwards. The evidence was too.
Stop it, keep it, delete it later. In that order.
How to check your own site
Three checks, none of which need Respira.
1. Count twice. Note the number of active plugins the Plugins screen shows. Then read the list WordPress loads from:
wp option get active_plugins --format=json If the option holds more entries than the screen shows, something is active that the screen is not listing. Every entry should be a plugin you recognise. An entry that is a bare PHP file you have never heard of deserves the same attention.
2. Read the top of the rewrite table.
wp rewrite list --format=csv | head -40
Look for a short pattern that matches almost anything, such as ^([^/]+)/?$ or
(.*), pointing at a query name you do not know. Some plugins that publish a post
type at the root of a site add a rule like this on purpose, so an unknown name is the signal,
not the pattern alone.
3. Read the REST index. Open /wp-json/ on your site and look at
namespaces. Each one belongs to WordPress, to your theme or to a plugin you can
name. One you cannot place is worth tracing.
If you find something: take a copy of the plugin's folder before removing anything, change every
administrator password, end all sessions, and treat the database password and the keys in
wp-config.php as read. A plugin with a route that updates its own code could read
that file.
What Respira checks now
Respira for WordPress lets an AI assistant such as Claude or ChatGPT work on your WordPress site, with a person approving what changes. Its security scan already compared core, plugins and themes with the known vulnerability database and verified core files. After this site, version 9.1.10 does the three checks above on every scan and says so in plain words:
- A plugin that is active and hidden from the plugin list, with the REST routes it serves and a list of its files and their hashes.
- A file WordPress loads that is not a plugin any list shows.
- A rewrite rule that answers your pages before WordPress does, measured against your real pages, naming the ones affected.
- Which plugin, theme or file serves each REST namespace.
And two tools for what comes after:
- Quarantine. The plugin is stopped without any of its own code running, and its folder is moved out of the plugins directory with every file renamed so the server cannot run it. Nothing is deleted, a manifest of every file is kept, and one step moves it back.
- Rebuild the rewrite rules from what is installed now, with the previous table kept, instead of editing the table by hand.
For sites where removing a plugin should never be an agent's decision, there is a new setting: the request waits for an administrator to press Approve in wp-admin, and the agent cannot do that part.
The prompt, for anyone already connected:
Run the security audit on my site with a deep scan. Tell me in plain words
whether anything is active that the plugin list does not show, and whether
any rewrite rule answers my pages before WordPress does. Change nothing. The same scan runs for every connected site from the security page of the dashboard, three sites at a time.
What this does not prove
How the plugin got onto the site is still not known. The site was one release behind on WordPress core at the time, and an old file manager plugin sat inactive on disk; either is a candidate and neither is proven. The plugin's own code was not kept. And everything above is what WordPress can see of itself: Respira does not read server logs or files outside the install, and a scan that finds nothing is evidence, not a guarantee.
Hidden plugins are the pattern of the past year. Wordfence has documented a must-use plugin that hides from three admin screens, and Patchstack counted 11,334 new WordPress vulnerabilities in 2025, nine in ten of them in plugins. A site's own admin screens are a poor witness to what the site is running. Count twice.
Questions
Can a WordPress plugin be active and not appear in the plugin list?
Yes. WordPress loads every plugin named in the active_plugins option. The Plugins screen in wp-admin draws its list through a filter called all_plugins, and a plugin can use that filter to remove its own row. The plugin keeps running and the screen shows one plugin fewer than the database holds. Comparing the active_plugins option with the list on the Plugins screen shows the difference.
Why do my WordPress pages show "No results found" or a 404 when the pages still exist?
WordPress matches an address against its rewrite rules in stored order and stops at the first match. A rule that matches any single-segment address, placed ahead of the page rule, takes every top-level page and routes it somewhere else. If that somewhere holds nothing, the visitor sees "No results found" or a 404 while the page sits untouched in the database. Pages with a parent, such as /about/team/, keep working, which is a useful clue.
What is wp-core-util?
A plugin found running on a live site on 1 October 2026, version 1.1.6, absent from the Plugins screen. It registered the REST namespaces wpu/v1 and wpccpu/v1, kept an API key and a list of Google crawler address ranges in the options table, and added the rewrite rule ^([^/]+)/?$ pointing at a post type named v_post. Its own status reported no posts and a disabled mode at the time it was found. Its source code was not read before it was removed.
Should I delete a plugin I suspect is malicious?
Stop it first, keep its files, delete later. Deleting through WordPress runs the plugin's own uninstall code one more time and destroys the files that could say how it got in. Take a copy of the folder, or quarantine it: remove it from the active list without running its hooks and move the folder somewhere the web server will not run it.
Written on 2 October 2026 from the cleanup's own notes. The site is not named.
Join the conversation
0 comments · Respira accountNo comments yet. Be the first to weigh in.