Super Speedy Search security review

Security › Super Speedy Search

Security score 100/100 · Version 5.95.2 · Reviewed 2 November 2026 · Source fingerprint 8741f3f

Summary

We reviewed Super Speedy Search 5.95.2 on 2 November 2026, and it scored 100/100 for security. The review was carried out by an AI agent working through our written security checklist; the first review that day read every first-party file line by line and scored 5.95.1 at 0/100. Each finding was reproduced on a private test site, and each fix was confirmed by a test suite that failed 27 checks on 5.95.1 and passes all of them on 5.95.2. The reviewed code was then merged unchanged into the main code line as version 5.96.0, so every fix below is in 5.96.0 and later.

What was found and fixed

The September review read the code structurally. This review traced every public, admin and search path to where its data ends up and tested each one over HTTP, which is why it found far more.

  • SECURITY-001, Critical, 25 points. A post_type value in a search URL was applied to the search without checking that the post type is searchable. A visitor could search post types that are not public, which on WooCommerce stores listed published coupon codes. Fix: requested post types are limited to the searchable set, and ignored if none remain. Fixed in: 5.96.0.
  • SECURITY-002, Critical, 15 points. WordPress hides password-protected posts from logged-out searches, but Super Speedy Search’s replacement search clause did not keep that rule. Protected posts appeared in results, and live search returned the start of their content. Fix: protected posts are excluded for visitors who have not entered the password, live search blanks their excerpt, and cached results are kept separate for logged-in and logged-out visitors. Fixed in: 5.96.0.
  • SECURITY-003, Critical, 25 points. With search tracking on (off by default), a visitor’s search term was stored and later shown on the admin Search Analytics table. The table’s JavaScript did not escape quote characters before building the HTML, so a crafted term could run script when an administrator viewed it. Fix: the table is built with DOM methods that treat every value as text. Fixed in: 5.96.0.
  • SECURITY-004, Critical, 25 points. Any user who can add a custom field to a post, Contributors included, chooses its key name. The Meta Search settings list printed key names without escaping, so a crafted key name could run script for an administrator. Fix: key names are escaped wherever they are printed. Fixed in: 5.96.0.
  • SECURITY-005, High, 25 points. Three attributes of the search box shortcode and block were printed into HTML attributes without escaping. A Contributor could add a shortcode whose attribute ran script for anyone viewing or previewing the post. Fix: every attribute is escaped, and the form action URL is escaped as a URL. Fixed in: 5.96.0.
  • SECURITY-006, Medium, 25 points. Combination suggestions built from a custom field were shown in the live search dropdown as HTML. Where that field is written by a lower-privileged role or an import feed, its value could run script for visitors. Fix: suggestion names are shown as text, while names containing ordinary entities such as & still display correctly. Fixed in: 5.96.0.
  • SECURITY-007, Medium, 15 points. The Search Manager role and the AI agent’s sss/test-query ability accepted any post type, so they could list titles of non-public types such as coupons. Fix: both, and the Relevance Coach, search only searchable post types. Fixed in: 5.96.0.
  • SECURITY-008, Low, 15 points. The Relevance Coach history could record and show the title of a draft, private or trashed post to a Search Manager. Fix: the coach saves only published posts to its history. Fixed in: 5.96.0.
  • SECURITY-009, High, 20 points. The public search routes had no limit on search length, word count or request rate, and a taxonomy lookup matched wildcard characters, so a visitor could make the database do a large amount of work per request. Fix: a new setting, Limit long searches to their first 20 words, on by default, searches the first 20 words or 256 bytes and never shows an error; wildcard characters are matched literally; include and exclude lists are capped; and on sites with a persistent object cache such as Redis, live search allows 300 requests a minute per visitor and answers HTTP 429 beyond that. Fixed in: 5.96.0.
  • SECURITY-010, Medium, 25 points. Some settings values were saved without cleaning and printed without escaping, which matters on multisite, where a sub-site administrator is not allowed to add script. Saved meta key names were also placed directly into SQL, so a key containing an apostrophe caused a database error. Fix: settings are cleaned on save and escaped on output, meta key names are bound as SQL values, and numeric weights are cast to numbers. Fixed in: 5.96.0.
  • SECURITY-011, Low, 0 points. With search tracking on, the search log kept every distinct term for ever, and very long terms were stored inconsistently. Fix: terms are cut to 255 characters before storing, and a daily job removes terms searched only once and not seen for 90 days, keeping every term that has a redirect or cache rule. Fixed in: 5.96.0.
  • SECURITY-012, Low, 0 points. The bundled update checker in the shared settings library included a developer build script that could be requested directly. It accepted no input, but had no reason to be in the plugin. Fix: the script is removed, and the shared library’s remaining file without a direct-access guard now has one. Fixed in: 5.96.0 (shared library 1.14.4).

The 70/100 published on 4 September 2026 reflected two findings. The public search cost finding is SECURITY-009 above. The other was in the shared licensing and update library that all our paid plugins carry:

  • Licence key in update URLs, Medium, 10 points. Update downloads included the licence key in the download URL, where web servers, proxies and WordPress’s own update records could store it. Fix: the licence key is now sent in a request header that goes only to our update server, with redirects turned off. Fixed in: 5.95.1 (shared library 1.14.3).

Version 5.96.2 also includes shared library 1.14.5, which stops the AI connection set-up from creating a user unless the person setting it up is allowed to add users.

What the review covered

AreaResultEvidence
Authentication, authorisation, ownership and request forgeryChecked14 admin ajax handlers, 12 Relevance Coach handlers, 11 AI abilities, 8 REST registrations, 2 background-process handlers and the settings form were traced. Requested post types are filtered to the searchable set on the search URL, the AI test query and the coach (SECURITY-001, 007, 008). Undo tokens are tied to the user who made them.
SQL injectionCheckedAbout 130 query sites, more than 120 using $wpdb->prepare(). Meta keys are bound or escaped in split meta search and both index builders, wildcard characters are escaped in both taxonomy substring searches, and weights are cast to numbers (SECURITY-010). The SQL changes were reviewed and approved before merging.
Cross-site scriptingCheckedAbout 190 PHP output sites and every DOM write in the 8 first-party JavaScript files were checked. The analytics table, meta key list, shortcode attributes, combination suggestions, settings values and the dropdown’s default image are all escaped or built as text (SECURITY-003 to 006, 010).
Server-side request forgery, open redirects and header injectionCheckedThe 7 outbound calls go to fixed hosts with wp_safe_remote_* and no redirects. The Retry-After header is an integer.
File uploads and path traversalCheckedThe plugin accepts no uploads. Every include and template path is fixed or on an allow-list.
Remote code execution and unsafe deserialisationCheckedNo eval or process calls in runtime code. The update checker’s build script, the only exec(), is removed (SECURITY-012).
XML parsingNot applicableThe plugin does not parse XML.
Secrets, privacy and data exposureCheckedPassword-protected content stays protected, including in cached results (SECURITY-002). Tracked terms are truncated and old one-off terms removed (SECURITY-011). The licence key travels only in a header to the exact update download address.
Business logic, replay and concurrencyCheckedThe rate limiter counts with atomic cache operations and never writes to the database. Rebuild state changes use compare-and-swap, and the tracking upsert is atomic.
DependenciesCheckedShared settings library 1.14.4. composer audit reported no advisories for the bundled MCP Adapter 0.5.0.
JWT, GraphQL and server-side templatesNot applicableThe plugin uses none of these.

Not scored

  • Request rate limit needs a persistent object cache. By our decision, live search counts requests only in a persistent object cache, so an anonymous search never writes to the database. Sites without one are not rate limited, and full-page ?s= searches are not rate limited on any site; each request is still bounded by the 20-word and 256-byte limit, the include and exclude caps and literal wildcard matching.
  • Taxonomy substring results. The taxonomy substring search returns every matching term rather than a capped number per taxonomy, because capping it would change what shoppers see. Its cost is bounded by the word limit and literal wildcard matching.
  • Search log retention. Terms searched more than once are kept until an administrator deletes them. Terms searched only once are removed after 90 days.
  • Password-protected posts in staff live search. When Log users in for REST search calls is off, live search treats every request as logged out, so it now hides password-protected posts from editors and administrators too. This errs on the side of hiding content.
  • Also fixed in 5.96.0, not a security finding. WooCommerce order searches that combined a search term with another filter, such as a customer, could return orders outside that filter. Those searches now use WooCommerce’s own order query.
  • Later hardening, not covered by this score. Version 5.96.1 makes the rate limiter count each visitor by their own address when the site sits behind Sucuri, QUIC.cloud, Bunny or Fastly, as it already did behind Cloudflare. It trusts each provider’s visitor-address header only from that provider’s published edge addresses, which are bundled in the plugin. Before this, visitors behind one edge node shared a single counter. This change was made after the review and is not part of the 100/100 score.

Back to all security scores

▲
▼
1/1