Suspect WP Options

The wp_options table has no size limit and nothing scans it, which makes it a comfortable place for a payload. How suspect rows are found without reading them.
What this check and mySites.guru tool looks at on your site
wp_options is WordPress’s general-purpose settings table. Anything can write to it, rows have no size limit, and nothing scans it. That combination makes it a comfortable place to keep a payload, because a file scanner cannot see into your database at all.
On every snapshot mySites.guru looks at that table and reports the rows that stand out. It asks four questions of each row, and none of them involves reading what is in it.
- Is the name nothing but hexadecimal characters? WordPress and its plugins name their settings after what the setting does. A name that is a bare string of 10 to 12 hex digits, anchored at both ends, is a label generated by a program. This is the strong signal.
- Is the name one a known malware family uses? The family this was built against keeps its payload under its own small set of option names, and hides some of them behind WordPress’s own transient prefixes. The wrapper is stripped exactly once before comparing.
- Do the first eight bytes look like compressed, encoded data? A cheap extra check on the head of the value.
- Do they look like an opening PHP tag? Kept because it costs nothing and catches other families, though it caught none of the rows on the site this was calibrated against.
No option values leave your server during the sweep
The check collects the name of the row, how many bytes it holds, whether WordPress loads it on every request, and the first eight bytes as a fingerprint. Eight bytes is enough to tell compressed data from plain text and far too little to be anything else. The contents of your settings stay on your site unless you ask to see one specific row.
The anchoring in that hex rule is doing real work. Formidable Forms writes frm_form_templates_ followed by 32 hex characters, and Elementor Pro writes elementor_pro_api_request_ followed by 15. A rule that matched a hex tail instead of the whole name would report every site running either of them as hacked.
Alongside the findings, the tool reports how many bytes of your options table WordPress loads on every single page view. That is a speed number rather than a security one, and it has no tier and no colour. It is counted against all four values WordPress accepts in the autoload column, because 6.6 added a new vocabulary and did not rewrite existing rows, so an upgraded site holds both generations forever.
Why size turned out to be no signal at all
The obvious rule for a table holding an 837,308 byte payload is a size threshold. Three real options tables were available to compare: one from a compromised site, the same site after it was cleaned, and a stock WordPress install.
| Signal | Infected site | Same site, cleaned | Stock WordPress |
|---|---|---|---|
| Option rows | 860 | 774 | 143 |
| Total option bytes | 4,807,624 | 1,393,043 | 86,015 |
| Largest single row | 837,308 | 372,553 | 30,181 |
| Rows over 64KB | 15 | 5 | 0 |
Option names matching ^[0-9a-f]{10,12}$ | 32 | 0 | 0 |
Size does not separate those three columns. The bare hexadecimal name separates them perfectly, and it is nearly free to test.
The case that settles it is rewrite_rules, which is part of WordPress itself, exists on every WordPress site there has ever been, holds the compiled permalink rules and grows past 64KB on any site with enough pages and custom post types. A tier that fires on that is measuring how big a site is. So a large row is context: listed with its byte count and a vendor label where the name is recognised, with no warning, no tier and no contribution to the number on your audit row. A site with nine large rows and nothing else gets a green tick.
There is a corollary worth stealing for your own tooling. Do not calibrate a size rule on “bigger than the malware we found”. The largest legitimate row measured so far is 1,891KB and the largest malicious one is 818KB, so that threshold clears the sick site and flags the healthy one.
A vendor name is a label, never a permission
Where a large row’s name is recognised, the tool prints a short vendor string beside it and nothing else. Attribution runs last, and only while a row is still judged clean, so a name that tripped any rule never reaches the vendor table at all. That ordering is what makes the list safe to be incomplete: an unrecognised large row renders in the same neutral list as a recognised one, just without a name beside it. A missing entry costs a missing word.
The label says who a row is named after, never who wrote it, because the name comes from the site being examined.
Every option row accounted for
Nothing in your settings table has a name that looks generated rather than chosen. No row holds compressed or encoded data. The large rows you do have are ones you can attribute to a plugin you installed on purpose.
How to fix it
- Treat this list as a place to look. It is the yellow, human-judged twin of Suspect Content rather than a red one.
- Start with the names, not the sizes. A row named after nothing is far more interesting than a large row named
_transient_something_sensible. - Recognise before you remove. Deleting the wrong settings row breaks a plugin, and there is no undo. If you do not know what a row is, search its name before touching it.
- Do not clean the database and stop there. If something is writing payload rows, it has a way in and something that keeps it running. Check your scheduled events and your must-use plugins and drop-ins in the same sitting.
- Ask. If a row looks wrong and you are not sure, that is exactly the kind of thing to send in.
What mySites.guru does about it
On every snapshot, twice a day, mySites.guru sweeps your options table and reports what stands out, without collecting a single option value. This check reports rather than accusing: it does not mark a site as hacked, because the cost of a false confirmation is telling a paying customer their site is infected when it is not.
A row the check actually accused can be read one value at a time, capped at 64KB and gated on that exact name, and then deleted. A large row it merely listed cannot be deleted from here, on purpose. The delete goes through WordPress’s own function so the options caches clear, and the result is then confirmed by reading the table again.
On a multisite network each site has its own options table, and mySites.guru reads the one belonging to the address you registered.
Suspect WP Options
mySites.guru checks every connected site for this automatically and flags it the moment it appears. These run twice a day on every connected site.
Further Reading
- Five New Checks for WordPress Hacks a File Scan Cannot See - the compromise this check was calibrated against, and the three-table comparison behind the rules
- wp_autoload_values_to_autoload() - the four values WordPress 6.6 introduced for the autoload column, and why counting autoloaded bytes the obvious way under-reports on any upgraded site
