← All audits

Audit #33 Benign

5 affected plugins · 250 combined active installs · baseline 1.1 → head 1.2 · closed 1mo ago

Actor: Mathew (wp.org @mathewt) — 5-plugin portfolio (~200 installs total)
Show full summary

Verdict: BENIGN. Five plugins published by author Mathew (mathewt) on wp.org — add-as-preferred-source (90 installs), browser-address-bar-color-changer (50), image-zoom-on-hover (30), disable-right-click-content-copy-protection (20), announcement-notification-bar (10) — all received a synchronized release on 2026-05-05 (07:35-07:41 UTC) that adds an opt-in deactivation-feedback telemetry module. We checked the entire portfolio: there is no supply-chain attack shape, no code-execution vector, no authentication bypass, and no covert functionality. Sites running these plugins are not compromised.

What the new module does: When a site administrator deactivates the plugin from the Plugins screen, a feedback modal appears asking why. On submit, the plugin POSTs a JSON payload (site_name, site_url, wp_version, plugin_version, deactivation reason, plus an optional admin_email field the user can fill in) to https://startling-malasada-45aa0d.netlify.app/.netlify/functions/feedback with a server-side X-Relay-Secret: airtable-2026-mathew header. This is a standard opt-in feedback shape — the same pattern used by Freemius, Appsero, and most premium-plugin SDKs — but with a personal Netlify auto-named subdomain instead of a branded vendor host.

Why we audited: The module surfaced in our daily diff-review (/beacon-audit-diff) on 2026-05-05 because two of Mathew's plugins (add-as-preferred-source, browser-address-bar-color-changer) shipped the same payload to the same throwaway-style Netlify endpoint with the same hardcoded relay-secret string. That cluster shape — single author, multiple plugins, fresh-domain telemetry endpoint, all updated within minutes of each other — is exactly the pattern we look for as a possible early-stage supply-chain attack signal. The deeper read confirms it isn't: the endpoint is alive (Netlify Function returns HTTP 405 on GET, the /privacy page returns 200), the relay-secret is in PHP-only and never reaches the browser, and the JSON payload contains no credentials, hashes, options, or arbitrary content.

Posture critique (not a security finding): The Netlify auto-generated subdomain (startling-malasada-45aa0d) is owned by Mathew's Netlify account, not a hijack risk, but visually reads as throwaway. We'd suggest Mathew use a branded domain in future releases — and disclose the telemetry in the readme — to avoid future audits triggering on the same posture.

Investigated — no compromise found.

Audit retained for the record. No action required.

Affected plugins (5)

All plugins covered by this incident report. Combined exposure: 250 active installs across 5 slugs.

Plugin version history

Every release on wp.org for this plugin. This audit found no malicious code; the version history is shown for reference.

  1. Earlier 1 earlier release
    • 1.0.1
  2. 1.1 Audit baseline Last clean release before incident
  3. Closure 102 days · 2026-01-23 → 2026-05-05

    wp.org closed this plugin pending review. No malicious code was found in any release; the closure reflects a policy decision (commonly: guideline compliance, vendor commercial-content rules, or extended unmaintenance). Releases below remain installed on existing sites and are not a security exposure.

  4. 1.2 Released after reopen PRT cleanup release — incident closed
  5. 1.2.1 Latest release Current release

Timeline

  • 2025-07-28mathewt wp.org account created
  • 2025-08-07 — first plugin published: image-zoom-on-hover
  • 2025-08-25disable-right-click-content-copy-protection
  • 2025-08-28browser-address-bar-color-changer
  • 2025-09-09add-as-preferred-source
  • 2025-11-19announcement-notification-bar
  • 2026-05-05 07:35-07:41 UTC — synchronized release across all 5 plugins, each adding a per-plugin class-{prefix}-deactivation-feedback.php and class-{prefix}-admin-notices.php

Actor footprint

PluginInstallsLatestTelemetry constant
add-as-preferred-source901.2AAPS_RELAY_SECRET
browser-address-bar-color-changer501.0.1BABC_RELAY_SECRET
image-zoom-on-hover301.0.2IZOH_RELAY_SECRET
disable-right-click-content-copy-protection20(post-repair)(assumed, same pattern)
announcement-notification-bar10(post-repair)(assumed, same pattern)

All *_RELAY_SECRET constants are set to the literal string airtable-2026-mathew. All plugins POST to the same endpoint.

Code under review

The deactivation-feedback file (e.g. includes/class-izoh-deactivation-feedback.php) registers an admin script that injects a feedback modal into the Plugins screen, hooks wp_ajax_*_submit_feedback, and on submit calls:

wp_remote_post(
    'https://startling-malasada-45aa0d.netlify.app/.netlify/functions/feedback',
    array(
        'headers' => array(
            'Content-Type'    => 'application/json',
            'X-Relay-Secret'  => IZOH_RELAY_SECRET,
        ),
        'body'    => wp_json_encode( $payload ),
        'timeout' => 15,
    )
);

The $payload array contains:

$payload = array(
    'reason'         => sanitize_text_field( $_POST['reason'] ?? '' ),
    'details'        => sanitize_textarea_field( $_POST['details'] ?? '' ),
    'admin_email'    => sanitize_email( $_POST['admin_email'] ?? '' ),  // user-fillable opt-in
    'site_name'      => get_bloginfo( 'name' ) ?: 'Unknown',
    'site_url'       => get_site_url() ?: 'Unknown',
    'wp_version'     => get_bloginfo( 'version' ) ?: 'Unknown',
    'plugin_version' => $this->version ?: 'Unknown',
);

No options, no user data, no credentials, no plugin content. Only the deactivating admin's site metadata + their typed reason + an opt-in email.

Live endpoint check

GET  https://startling-malasada-45aa0d.netlify.app/                             → 404
GET  https://startling-malasada-45aa0d.netlify.app/.netlify/functions/feedback   → 405 (Method Not Allowed)
GET  https://startling-malasada-45aa0d.netlify.app/privacy                       → 200

Endpoint is live and configured. Method 405 confirms the Netlify Function exists and only accepts the documented POST. The /privacy page exists, satisfying GDPR's basic disclosure requirement.

Hijack-indicator matrix

IndicatorResult
Sole committer for ≥2 years?n/a — Mathew has been the only committer since each plugin's first release
Sudden new committer before closure?no — author unchanged across all 5 plugins
Author profile drift?no — mathewt profile stable since 2025-07-28
Code-level malware patterns?no — no eval/base64-decode/file_put_contents/include of remote content
Outbound C2 / known bad domains?no — single endpoint to a Netlify auto-name, payload contains no executable content
New SVN credentials before closure?no — no closure; same author throughout
Plugin closed by wp.org?no — all 5 are active

Clean matrix → benign.

Why this isn't a supply-chain attack

1. The endpoint receives, doesn't serve. The plugin POSTs metadata; the response is discarded (wp_remote_post return value is not used). There is no pathway for the Netlify Function to return code that gets executed. 2. The "secret" is server-to-server. IZOH_RELAY_SECRET is a PHP constant only used in the HTTP header. The browser never sees it. If the Netlify Function were to be reverse-engineered, an attacker would only gain the ability to spam Mathew's feedback inbox — they cannot affect plugin behavior on user sites. 3. Trigger is one-shot, on deactivation only. Not on activation, not on a schedule, not on every page load. A site that deactivates the plugin sends one message and stops; a site that doesn't deactivate sends nothing. 4. No version pre-fix or fingerprint check. The module doesn't check whether the running site is "interesting" before phoning home (e.g. doesn't look for ecommerce, login forms, admin sessions). It's just metadata. 5. The pattern matches established premium-plugin SDKs (Freemius, Appsero, MonsterInsights). Mathew is doing the same thing with a less polished setup.

Comparable cases

  • Audit #15 (content-egg) — different shape (historical PHP-object-injection chain) but same outcome: review-tier signal, deep-dive verdict benign with cleanup notes.
  • Freemius SDK pattern — known-good telemetry shape that ships in 100s of wp.org plugins. Mathew's module is structurally identical.
  • WPFactory cross-selling library FP class (memory: project_wpfactory_cross_selling_fp_2026-04-27) — different actor, but same lesson: a vendor running their own opt-in admin telemetry is not by itself a security finding.

Reviewer notes

The reason this audit was opened was the actor-cluster shape, not any individual file pattern. WP Beacon's daily diff-review surfaces clusters where one author ships the same novel telemetry across multiple plugins on the same day — this is a high-value signal for early supply-chain attack detection (see the scroll-top / Benjamin / @milkitall case from audit #12 where a similar cluster was malicious). For Mathew, the cluster turned out to be a small developer adopting a feedback workflow. The audit is published as reassurance: site admins running any of the 5 plugins can see at a glance that the unfamiliar Netlify endpoint is intended, opt-in, and not a security risk.