Repository navigation
Releases
Releases · omens-agency/omens-wp-plugin-audit
v1.0.11
Fixes from the November 10 dev team audit (three independent reviews before the first live filing month). No new checks.
- A run that cannot be saved now fails loudly.
omens_audit_trim()cut by bytes, so a cut inside a multibyte character (a curly quote or an en dash in a post title) left invalid UTF-8; the options table refuses such a value, the store's result was ignored,wp omens-audit runprinted Success, andexportreturned the previous run. Trimming now cuts on a character boundary, every finding's text is scrubbed of invalid UTF-8, the store retries once with the text made safe (invalid bytes removed, four-byte characters encoded forutf8tables), and the run reportsstored. The CLI exits 1 when a real run was not saved. a11y.link-emptyno longer puts the cache-buster in a fingerprint. WordPress echoes the request's query string into pagination and share links, and the audit fetches pages with?omens-audit=<time>, so the same empty link had a new subject, and would have been a new task, on every run.- The updater cannot take a request down. The update checker is built inside a
try, and the release-asset mode falls back when the Plugin Update Checker copy that loaded (another plugin's) predatesREQUIRE_RELEASE_ASSETS. - Nothing outside a check can lose the run. The sample, theme details and the four inventories each fail on their own and are listed in a new
errorsarray; the cron fallback catches everything;get_mu_plugins()is loaded before use. - Loopback fetches stay on the site. Redirects are followed one hop at a time and only to this site's own host; a page that redirects elsewhere (a privacy page pointing at a hosted policy) is recorded as not fetched instead of being audited as if it were the site's markup. A followed redirect keeps the cache-buster.
seo.staging-links: a staging suffix must end the host, sowww.test.com,cdn.local.example.netandshop.wpengine.com.auare no longer reported; the SQL prefilter is built from the same suffix list as the pattern; revisions and ignored meta keys are excluded in SQL instead of using up the row limit, and results are ordered; slider placement is matched in PHP against rows fetched once per run, instead of up to 25 table scans per slider; single-quoted shortcode attributes andlayerslider_vccount as placements; draft tables are skipped; the unused-slider note says what the check knows ("found no published page that uses these") instead of "nobody sees these".
v1.0.10
seo.staging-linksreads Slider Revolution and LayerSlider. Both keep slides in their own tables (revslider_*for v6 and v7,layerslider), which 1.0.8 never read, so a slide loading its images from staging was invisible. A slider holding a staging or local host joins the site's content finding only when it is placed on a published page (its shortcode, an Elementor or WPBakery widget, a theme's slider setting, or a theme option); the task names the slider and the page and adds "open the slider and save it once" so the slider's own cache rebuilds. Sliders placed nowhere, or in LayerSlider's trash, are listed once per site at info, as cleanup. Backup, revision and style tables are skipped. A slider reference counts only as a word of its own, so an admin-menu cache that lists every slider asdata-alias="..."(Adminimize) places none of them. The November 10 sweep: two home-page sliders loading from staging (estebaninteriors.com, hotelsanbuena.com), one placed slider with alocalhostsetting (crosbyattorney.com), and seven unused sliders on four sites.
v1.0.9
- Plain-language description on the Plugins screen. Client administrators see the plugin's description, and it read as internal notes ("a stateless audit sensor", WP-CLI commands). It now says what the plugin does for the site. No change to the checks or the export.
v1.0.8
- New check,
seo.staging-links(serious): a production site that still links to, or loads files from, a local or staging host. A database push from staging or a local copy carries the old host into page content, WPBakeryurl:attributes, Elementor data, theme options, widgets, menus and Gravity Forms settings, and a theme file can carry it too. Visitors land on staging, images break when staging is wiped, and a form confirmation can send everyone who submits it to the wrong site. The first fleet sweep (November 10) found it on 18 of 35 sites. The check reads stored content in plain, JSON-escaped and URL-encoded form, and the active theme and parent theme files (bounded: 6,000 files, 600 KB each, no dot-folders,node_modulesorvendor). It files at most three findings per site, one per place to fix: content (serious), form settings (moderate, or serious when a confirmation redirects to staging) and theme files (serious, routed to a person on purchased themes). Each finding names the hosts and the places and gives thewp search-replacedry run for the bare host. Production only; the site's own host never counts. Skipped as noise, each one seen in the sweep: migration and search-replace history, update mirrors (WP Engine's), licence clone records (Freemius), caches and logs, attachment source URLs, visitor-submitted posts (Flamingo, orders), drafts and revisions, and a loopback address inside theme code (a guarded local-development branch). Filters:omens_audit/staging_link_suffixes,omens_audit/staging_link_ignore_hosts,omens_audit/staging_link_ignore_options,omens_audit/staging_link_ignore_post_types,omens_audit/staging_link_page_types. inventory.site_facts.theme_plugin_sourcesexports as a JSON object when empty. PHP encodes an empty map as[], so a site whose theme registers no plugins exported a list where every other site exported an object.
v1.0.7
a11y.img-alt-filenameaccepts a one-letter alt. A single letter is a deliberate description of a glyph icon (the X share button, an "f" for Facebook), not a filename accident, but the filename rule readx.pngwithalt="X"as a repeat of a meaningless name (the last false positive of the November 8 sweep, on blackbirdlab.org). An alt that is the whole filename with its extension still fires.legal.consent-missingrecognizes other consent platforms. A site on WPConsent, Beautiful Cookie Consent, Complianz, Cookiebot, iubenda, OneTrust, Termly, Osano, Usercentrics, Didomi, Real Cookie Banner, GDPR Cookie Compliance or Cookie Notice has a banner; it is just not the house platform, so the check records an info line ("Consent banner is X, not CookieYes") instead of a serious task. Serious stays for a site with no banner at all. Jake, November 8: Swedish Program runs WPConsent and MiresBall runs Beautiful Cookie Consent on purpose. Filter:omens_audit/other_consent_platforms.- The export carries site facts for the bundled-plugin rule.
inventory.site_factsreports whether the active theme or its parent offers an update, which roles have users and what each can do (a level read from capabilities), whether registration is open, and which plugin slugs the theme's own PHP registers. The central run uses them to decide whether a theme-bundled plugin's vulnerability is worth a task (a theme update on offer, a flaw that needs no login, or an account that can reach it), and to confirm a theme alert against the installed theme version. Until now the run read these with a separatewp evalover SSH; with 1.0.7 the only commands it runs on a site arewp omens-audit runandwp omens-audit export, as the plan says. Read-only, nothing compared on the site. A role WordPress cannot resolve is reportedunresolvedand the central run assumes it can reach anything; the theme scan is bounded (entries, bytes, file size, regular files inside the theme only), yields nothing unless the theme ships TGMPA, records the file each slug came from, and reports an error instead of stopping the run. seo.description-missingskips builder templates and fragments. The check counted every publicly queryable post type, so Elementor kits and headers, Royal Addons templates and mega-menu items, ThemeREX layouts, JupiterX code and font records, slider text, Events Calendar venues and organizers, GravityView views and WP Timeline entries were "entries without a meta description": about 300 of the 1,901 the fleet reported on November 8, none of them a page a searcher lands on. The sample'somens_audit_template_post_types()list is now the check's default skip (theomens_audit/description_skip_typesfilter still adds to it) and gained those fragment types.
v1.0.6
currency.pluginsrolls bundled and unlicensed plugins into one info line per site. A pending update whose transient entry carries no download package cannot be applied from the site (the plugin shipped inside a purchased theme, or its licence is not active here; WordPress says "Update package not available"), so filing it as a moderate task every month asked for something maintenance cannot do. Those plugins now appear once per site, listed, with the decision the PM records instead (direct licence, removal, or accept with the vulnerability watch). Plugins with a package keep their per-plugin findings. Jake's call, November 8, with the like-for-like rebuild offer for abandoned themes (Management/decisions-log.md).
v1.0.5
Two false positives found while working the moderate findings of the fleet dry run. No new checks.
a11y.link-emptyskips WordPress core's image lightbox trigger button, whosearia-labelis bound at runtime by the Interactivity API (data-wp-bind--aria-label), the same waya11y.img-alt-missingalready skips the lightbox image.- The URL sample skips page-builder template post types (Elementor library, ThemeREX layouts, Oxygen, ElementsKit and the like). Their permalinks redirect to the front page and the redirected request carries no cache-buster, so on three sites the audit kept reading an hour-old cached copy of
/under another name after a fix had landed. Filter:omens_audit/template_post_types. currency.house-pluginsno longer asks a custom build foromens-wp-plugin-seowhen Yoast, Rank Math or All in One SEO is active. Yoast is the house SEO pick (plugin baseline, 2026-08-31), so a starter build on Yoast is the normal case, not a gap.
v1.0.4
Fixes from the serious-findings review of the first fleet dry run. No new checks.
security.admins-without-2fafiles nothing while a 2FA plugin is active. Jake's call (November 8): enrolment is the plugin's job once it is enforced. The check now records one info line per site (how many administrators are enrolled, whether WP 2FA enforces the Administrator role, and the names not yet enrolled, kept in the evidence for the monthly report) and stays serious only when no provider is active. 151 would-be tasks across 34 sites become 34 info lines.seo.sitemapfinds the sitemap the way a crawler does. It reads robots.txtSitemap:lines first, then the detected plugin's default, then/sitemap_index.xml,/sitemap.xmland/wp-sitemap.xml, and accepts the first body that is a sitemap.<loc>values wrapped in CDATA (AIOSEO) are parsed. Four of the five fleet findings were the check's own: two AIOSEO indexes read as empty, Slim SEO's/sitemap.xmlnever tried, and a Yoast site mis-detected as Omens SEO.- SEO-plugin detection prefers an active Yoast, Rank Math or All in One SEO over the Omens SEO fallback, which used to win whenever a theme defined
omens_seo_get_description().
v1.0.3
Fixes from the first fleet dry run (35 sites, November 7). No new checks.
security.php-in-uploadsnow has three tiers and a signature allowlist. Of its 36 criticals on the first fleet run, 28 were files plugins put under uploads themselves (Redux Customizer class stubs, Ajax Load More templates, Calendarize It help, Ultimate Addons icon maps, LearnDash and WPForms guards, the Mailchimp log), 6 were zero-byte remnants of a cleaned infection, and 2 were a live cookie-triggered backdoor that read exactly like the guards around it. The allowlist entries now carry a content signature (contains/starts), so a dropper named like a plugin file does not ride them; anyindex.phpthat only exits, comments or sends a header is a guard whatever its spelling; a file with no PHP in it is filed as inert at moderate; and a file whose code reads request input, decodes or evaluates strings, or runs commands is filed as a suspected web shell with the incident steps first. Filters:omens_audit/uploads_allow(entries may now be arrays),omens_audit/uploads_shell_markers.a11y.img-alt-missingskips WordPress core markup that is correct as printed: the image-lightbox template (data-wp-bind--alt) and comment avatars fromget_avatar().
v1.0.2
- Platform counters are moderate and point at the template. Jetpack Stats and the Pressable visit counter (Fathom Analytics, formerly Gauges; the platform injects it on every Pressable site) are now the
hosting analyticstracker category indata/trackers.json, solegal.tracker-driftfiles them at moderate instead of serious and its fix names the Legal Pages privacy template'sHOSTING_ANALYTICSblock (or the Pressable Insights switch, for the counter) instead of a cookie-table row. Jake's call, 2026-11-07, after both pilots produced the same two serious findings and the fleet would have produced about 120. - A check that returns its one
cannot-runfinding (for examplecurrency.pluginswhen theupdate_pluginstransient is empty, whichwp plugin installleaves behind) is recorded with statuscannot-runinstead ofran, so the central run does not count its open tasks as re-checked. The central run guards against this on its side as well.