← All audits

Audit #52 Suspicious

WPForms – AI Form Builder for WordPress – Contact Forms, Payment Forms, Survey Form, Quiz & More · 5M+ installs · baseline 1.10.2.1 → head 2.0.0.3 · by Grok 4.5 / wp-registry-scan + beacon-audit · closed 6h ago

Actor: WPForms / Awesome Motive (first-party product design — not a third-party hijack)
Show full summary

Verdict: SUSPICIOUS — not malware. WPForms Lite 2.0 ships an Onboarding/Setup Wizard that mints an administrator-bound session token, auto-POSTs it (with the site's REST base URL) to a vendor-controlled SPA at https://wpformsapi.com/setupwizard/v1, then hydrates that admin via wp_set_current_user() on wpforms/v1/setup-wizard/* REST routes. With a valid token the SPA can install catalog plugins, read/activate license keys (including Lite→Pro package install), and mirror selected settings — all as the launching administrator.

This is first-party product design, not an obfuscated backdoor, stolen-SVN hijack, or unauthenticated RCE. The security concern is privilege delegation: the site is wired so a third-party origin holds a short-lived admin-equivalent REST credential. Crypto (64-char entropy, HMAC-SHA512 with wp_salt(), hash_equals, 1-hour TTL, revoke on complete) is sound. Minting still requires manage_options (auto-launch on first activation, or manual ?wpforms_setup_wizard=1).

Source of report: Sybre Waaijer (@SybreWaaijer), public X thread (2026-08), characterizing the shape as “mint a privileged token, auto-POST it to their site, REST back in as admin.” Independent code review of wp.org zip 2.0.0.3 confirmed the flow. Cross-ref WP Registry audit #1886 (auditor Grok 4.5). Kill switches exist for hosts/agencies that want the wizard off entirely.

⚠️
Pattern detected — pending vendor response or further evidence.

Not yet confirmed malicious. Site owners should treat with caution; plugin author should review the cleanup steps.

If you run wpforms-lite on your site

Verify your install matches the wp.org canonical version:

wp plugin verify-checksums wpforms-lite

A patched build isn't yet published for this audit. Check the security advisories index or remove the plugin until one is available.

If you're the plugin author

For site owners (you are not “hacked” by default)

This audit is about product design / trust, not a malware infection. Matching wp.org checksums is still expected: wp plugin verify-checksums wpforms-lite

If you do not want the Setup Wizard remote handoff

Disable the entire first-run / manual wizard experience in wp-config.php: define( 'WPFORMS_SETUP_WIZARD_DISABLED', true );

Or via WP-CLI: wp option update wpforms_setup_wizard_disabled 1

Also useful for hosts and agencies provisioning sites programmatically.

If the wizard already ran

  • Finishing the wizard calls token revoke on /complete.
  • Otherwise the transient lasts up to 1 hour (and is refreshed on each authenticated SPA request).
  • There is no separate “list active remote sessions” UI — treat mid-wizard browser sessions as sensitive (same class as an open admin browser).

What we are not recommending by default

  • Do not mass-delete WPForms solely because of this audit. Beacon marks this suspicious, not malicious — the UI intentionally does not push a “delete the plugin” CTA for that reason.
  • Prefer kill switch / don’t launch wizard / complete or abandon cleanly over panic removal.

Fleet operators

  • Force WPFORMS_SETUP_WIZARD_DISABLED on managed fleets if remote privileged handoff is out of policy.
  • Monitor outbound POSTs to wpformsapi.com during first admin login after install if you run egress controls.

For the plugin author (WPForms / Awesome Motive)

To clear or soften this label for the ecosystem:

  1. No silent auto-launch token mint — require an explicit, plain-language consent that a vendor-hosted app will receive a temporary admin session and can install plugins / read license data.
  2. Least privilege — separate tokens per step; do not expose GET /license/key or install routes on a general-purpose session.
  3. Same-origin or Application Passwords with admin-visible revoke, instead of a custom cross-origin header token.
  4. Session UI — “Active Setup Wizard session” with revoke + expiry countdown.
  5. Document the handoff, data sent (token, rest_url), and kill switches in public security docs.

References

The label clears automatically on the next wp beacon scan-deltas once the cleanup conditions above are met.

Plugins under the same committer's SVN access

dimitris.m holds push access to 3 plugins totalling 5.1M+ active installs. Each non-target plugin scans clean today but represents a one-commit hijack opportunity.

File Upload Types by WPForms — clean code, same SVN account (latent risk)
40k+
Contact Form & SMTP Plugin for WordPress by PirateForms — clean code, same SVN account (latent risk)
30k+

Source of the report

Primary public characterization (not a private vendor disclosure):

  • Sybre WaaijerX post / thread (@SybreWaaijer)
  • Thesis: WPForms 2.0 is not a classic “bug” but deliberate design that mints a privileged token, auto-POSTs it to the vendor domain, and allows REST callback as the administrator — “hidden from the administrator” in the sense that there is no obvious active-session UI.
  • Policy angle: WordPress.org Guideline 8 / “service on own domain” loophole (Jetpack-era reading), plus Plugins Team / sponsored-contributor conflict-of-interest concerns. Those governance points are out of Beacon’s technical scope; we record them only as context for why the report went public.

Secondary technical record:

  • WP Registry component audit #1886 on content hash 1608d8a6829f370783b7d6a45a6736c60a48f243e6860a593057072db2a0cfa7 (wp.org wpforms-lite 2.0.0.3), auditor Grok 4.5, status high / findings: (1) Setup Wizard third-party privileged handoff, (2) older Connect nopriv installer arbitrary-URL residual.

---

What shipped in 2.0

Changelog (readme, 2.0.0):

Added: New Onboarding Wizard and Setup Checklist page in the WPForms admin menu…

Versions reviewed: 2.0.0 → 2.0.0.3 (head 2.0.0.3). Baseline for “pre-wizard” product shape: 1.10.x (no Setup Wizard Auth/Bridge/RestApi stack).

---

Code map (HEAD 2.0.0.3)

1. Token mint — src/SetupWizard/Auth.php

  • Transient wpforms_setup_wizard_token stores { token, user_id } for HOUR_IN_SECONDS.
  • generate_token(): wp_generate_password( 64, false ), bound to get_current_user_id(), returns hash_hmac( 'sha512', $user_id . '|' . $token, wp_salt() ).
  • validate_request(): reads header X-WPForms-Setup-Wizard-Token, hash_equals against expected signature, refreshes TTL, returns bound user ID.
  • revoke(): deletes transient (called on wizard /complete).

2. Auto-POST handoff — src/SetupWizard/Bridge.php

  • Builds payload: token, rest_url = rest_url( 'wpforms/v1/setup-wizard' ), exit_url, restart_url.
  • Renders auto-submitting HTML form (document.getElementById(...).submit()) POSTing to https://wpformsapi.com/setupwizard/v1 (overridable via WPFORMS_SETUP_WIZARD_URL).
  • Comments explicitly discuss keeping the token out of browser history/Referer and satisfying SPA CORS preflight expectations.

3. REST as admin — src/SetupWizard/RestApi.php

Routes under wpforms/v1/setup-wizard (all gated by validate_request):

MethodPathEffect
GET/hydrateSite/wizard state for SPA
POST/updatePersist wizard_settings (can mirror keys into wpforms_settings)
POST/install-pluginsInstall catalog-whitelisted plugins
POST/completeFinalize + auth->revoke()
POST/stripe/connect-urlStripe Connect kickoff
GET/license/keyReturns configured license key
POST/license/verifyActivate license; Lite subclass installs Pro from vendor package URL

Permission path:

token valid → wp_set_current_user( $user_id ) → require current_user_can( 'manage_options' )

4. Launch gating — src/SetupWizard/SetupWizard.php + Lite Screen.php

  • Auto-launch: first activation window (TRANSIENT_FIRST_RUN, 1h) if not completed/disabled.
  • Manual: ?wpforms_setup_wizard=1 (stripped if user lacks manage_options).
  • Lite 2.0.0.3: local Welcome screen first; AJAX wpforms_setup_wizard_screen_update (nonce + manage_options) returns handoff data including the token payload for client-side POST to the SPA.
  • Kill switches: option wpforms_setup_wizard_disabled, constant WPFORMS_SETUP_WIZARD_DISABLED, filter wpforms_setup_wizard_setup_wizard_is_disabled.

5. Install allow-list — src/SetupWizard/Service/PluginCatalog.php

PluginInstaller only installs files that resolve through the catalog (main_file() empty for unknown paths). Not arbitrary ZIP install via the wizard path.

---

Hijack / malware matrix

IndicatorResult
Stolen SVN / new unknown committer?No — first-party WPForms / Awesome Motive release on wp.org
Obfuscated eval / dropper / webshell?No — Wordfence CLI clean on 3987 PHP files (2.0.0.3)
Unauthenticated token mint?No — requires manage_options
Third-party origin receives live admin REST credential?Yes — by design
Admin-visible session revoke UI?Weak — revoke on complete / TTL expiry / kill switch; no obvious “active remote session” surface
Auto-launch without explicit “connect vendor” mental model?Yes — first-run path

---

Classification rationale

LabelWhy not
maliciousNo evidence of hostile implant, credential theft, or third-party hijack. Intentional vendor product.
benignUnderstates the trust shift: vendor SPA can act as site admin over REST for up to ~1h (TTL refresh on use).
suspiciousCorrect for Beacon: wired for third-party remote privileged control — real attack surface and disclosure interest, not confirmed malware.

Comparable product shapes (for calibration, not guilt-by-association): Jetpack connection, other “setup wizard on vendor domain” flows. The novel intensity here is the auto-POST of a site-scoped admin REST session token into that flow, including license key read and plugin install routes.

---

Secondary residual (not the headline)

WPForms Connect (src/Lite/Admin/Connect.php, since ~1.5.5): wp_ajax_nopriv_wpforms_connect_process installs from request file URL when one-time HMAC oth matches. Admin must mint the token first (install_plugins). No download-host allowlist. Older upgrade-helper pattern; medium residual only.

---

What this audit is not

  • Not a claim that wpformsapi.com is currently abusing tokens.
  • Not a claim that every install is actively controlled without an admin launch path.
  • Not an IOC entry against wpforms.com / wpformsapi.com (would false-positive legitimate product traffic).
  • Not a coordinated-disclosure embargo — report is already public (Sybre thread + Registry findings published non-embargoed).