Skip to content

Releases

v1.0.11

Choose a tag to compare

@jmcraigmile jmcraigmile released this 11 Nov 03:35

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 run printed Success, and export returned 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 for utf8 tables), and the run reports stored. The CLI exits 1 when a real run was not saved.
  • a11y.link-empty no 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) predates REQUIRE_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 errors array; 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, so www.test.com, cdn.local.example.net and shop.wpengine.com.au are 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 and layerslider_vc count 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

Choose a tag to compare

@jmcraigmile jmcraigmile released this 10 Nov 23:21
  • seo.staging-links reads 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 as data-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 a localhost setting (crosbyattorney.com), and seven unused sliders on four sites.

v1.0.9

Choose a tag to compare

@jmcraigmile jmcraigmile released this 10 Nov 22:31
  • 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

Choose a tag to compare

@jmcraigmile jmcraigmile released this 10 Nov 22:20
  • 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, WPBakery url: 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_modules or vendor). 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 the wp search-replace dry 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_sources exports 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

Choose a tag to compare

@jmcraigmile jmcraigmile released this 10 Nov 19:09
  • a11y.img-alt-filename accepts 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 read x.png with alt="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-missing recognizes 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_facts reports 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 separate wp eval over SSH; with 1.0.7 the only commands it runs on a site are wp omens-audit run and wp omens-audit export, as the plan says. Read-only, nothing compared on the site. A role WordPress cannot resolve is reported unresolved and 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-missing skips 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's omens_audit_template_post_types() list is now the check's default skip (the omens_audit/description_skip_types filter still adds to it) and gained those fragment types.

v1.0.6

Choose a tag to compare

@jmcraigmile jmcraigmile released this 08 Nov 21:06
  • currency.plugins rolls 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

Choose a tag to compare

@jmcraigmile jmcraigmile released this 08 Nov 20:50

Two false positives found while working the moderate findings of the fleet dry run. No new checks.

  • a11y.link-empty skips WordPress core's image lightbox trigger button, whose aria-label is bound at runtime by the Interactivity API (data-wp-bind--aria-label), the same way a11y.img-alt-missing already 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-plugins no longer asks a custom build for omens-wp-plugin-seo when 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

Choose a tag to compare

@jmcraigmile jmcraigmile released this 08 Nov 04:28

Fixes from the serious-findings review of the first fleet dry run. No new checks.

  • security.admins-without-2fa files 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.sitemap finds the sitemap the way a crawler does. It reads robots.txt Sitemap: lines first, then the detected plugin's default, then /sitemap_index.xml, /sitemap.xml and /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.xml never 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

Choose a tag to compare

@jmcraigmile jmcraigmile released this 08 Nov 04:11

Fixes from the first fleet dry run (35 sites, November 7). No new checks.

  • security.php-in-uploads now 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; any index.php that 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-missing skips WordPress core markup that is correct as printed: the image-lightbox template (data-wp-bind--alt) and comment avatars from get_avatar().

v1.0.2

Choose a tag to compare

@jmcraigmile jmcraigmile released this 07 Nov 23:11
  • 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 analytics tracker category in data/trackers.json, so legal.tracker-drift files them at moderate instead of serious and its fix names the Legal Pages privacy template's HOSTING_ANALYTICS block (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-run finding (for example currency.plugins when the update_plugins transient is empty, which wp plugin install leaves behind) is recorded with status cannot-run instead of ran, so the central run does not count its open tasks as re-checked. The central run guards against this on its side as well.