WP Manifestindependent plugin directory
manifest / unclassified / wp-rekuest-cve-client

Rekuest CVE Client

A WordPress plugin that checks the site against the Rekuest CVE scanner and reports the result to Zabbix.

★ 0stars

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/rekuest/wp-rekuest-cve-client/archive/refs/heads/main.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Rekuest

Rekuest CVE Client

A WordPress plugin that checks the site against the Rekuest CVE scanner and reports the result to Zabbix.

The plugin builds an inventory of the site and sends it to the scanner, which returns the known vulnerabilities affecting the installed versions. The plugin then publishes the outcome on a token-protected JSON endpoint. The same format ("schema 1") is used by the Laravel package and by the script for Plesk servers, so a single Zabbix template covers every client.

Note: the plugin is only useful if you have access to an instance of the Rekuest CVE scanner (URL and token are provided by Rekuest). Without a scanner it does nothing. The code is public for transparency and so that sites can update from GitHub releases. The admin interface is in Italian.

Contents

Features

  • Complete inventory:

    • WordPress and PHP versions;
    • every installed plugin and theme, including inactive ones, because their files stay reachable over HTTP;
    • Plesk version (from the version file, or set manually);
    • Composer packages (vendor/composer/installed.json, Composer 1 and 2, Bedrock included).
  • Scanner check at most once every 55 minutes, with caching, a lock against concurrent requests and a retry after an error.

  • An unreachable scanner never makes the site look clean: the endpoint reports the error and keeps the last valid result.

  • Status endpoint for Zabbix on two transports:

    • REST rekuest-cve/v1/status;
    • admin-ajax.php as an alternative.

    It is protected by a token, an IP allowlist and a limit on failed attempts.

  • "Settings → Rekuest CVE" page with:

    • the last result and the vulnerable packages;
    • the Verifica ora ("Check now") button;
    • status token generation;
    • an inventory preview.
  • "Vulnerabilità note" ("Known vulnerabilities") dashboard widget.

  • WP-CLI commands for checks, inventory and diagnostics.

  • Automatic updates from GitHub releases, with the package's ed25519 signature verified before installation.

  • Multisite: network activation, a single inventory and a single endpoint on the main site.

  • No runtime dependencies. The plugin sends no email and changes nothing on the site: Zabbix handles notifications.

Requirements

Minimum Notes
WordPress 5.0 Automatic updates need WordPress 5.2+ or PHP 7.2+, to verify the signature with libsodium.
PHP 5.6 Tested up to PHP 8.4.
Scanner Rekuest CVE with the /api/v1/ API URL and token are provided by Rekuest.

Installation

  1. Download rekuest-cve-client.zip from the latest release. Do not use the "Source code" archive generated by GitHub: it is the source tree, not the installable package.

  2. In WordPress go to Plugins → Add New → Upload Plugin and activate the plugin. On multisite, network-activate it.

  3. Configure the scanner in one of two ways:

    • with constants in wp-config.php (recommended);
    • from the Settings → Rekuest CVE page (on multisite: Network Settings → Rekuest CVE).
    define( 'REKUEST_CVE_SCANNER_URL', 'https://cve-scanner.example.com' );
    define( 'REKUEST_CVE_SCANNER_TOKEN', '…' ); // provided by Rekuest
    define( 'REKUEST_CVE_STATUS_TOKEN', '…' );  // generated for this site: wp rekuest-cve token
  4. If the status token is not in wp-config.php, generate it with the Genera token di stato ("Generate status token") button. The token is shown only once: copy it right away.

  5. Press Verifica ora ("Check now"), or run wp rekuest-cve check --force.

  6. In Zabbix, create a host with:

    • Host name equal to the host shown on the settings page;
    • template "Rekuest CVE by HTTP";
    • macro {$CVE.STATUS.URL} = the endpoint URL;
    • macro {$CVE.STATUS.TOKEN} = the status token.

Configuration

Every setting can be defined as a constant in wp-config.php or on the settings page. A constant takes precedence, and the matching field on the page becomes read-only.

Constant Default Description
REKUEST_CVE_SCANNER_URL — Base URL of the scanner, without /api/…. https:// only: with an http:// URL the client counts as not configured.
REKUEST_CVE_SCANNER_TOKEN — Scanner bearer token.
REKUEST_CVE_STATUS_TOKEN — Status endpoint token, at least 32 characters. Empty or too short: endpoint disabled.
REKUEST_CVE_HOST home_url() domain Value of the host field, equal to the Zabbix "Host name". On multisite the default is the main site's domain.
REKUEST_CVE_ALLOWED_IPS no restriction IPs or CIDRs (IPv4/IPv6) allowed on the endpoint, comma-separated.
REKUEST_CVE_CACHE_TTL 3300 Result freshness, in seconds (minimum 60).
REKUEST_CVE_TIMEOUT 15 Timeout of the scanner call, in seconds (1–120).
REKUEST_CVE_VERIFY_SSL true Scanner certificate verification. Can be turned off only with this constant, and only for local development.
REKUEST_CVE_PLESK_VERSION detected Plesk version, when /usr/local/psa/version is not readable (the usual case with open_basedir).
REKUEST_CVE_INCLUDE_COMPOSER true Send the Composer packages found.
REKUEST_CVE_UPDATES true false disables updates from GitHub releases (e.g. sites managed with Composer).
REKUEST_CVE_AUTO_UPDATE WordPress decides true/false forces or prevents automatic updates of the plugin.

Filters:

Filter Default Purpose
rekuest_cve_composer_vendor_dirs 4 paths vendor/ directories where installed.json is looked up.
rekuest_cve_plesk_version_file /usr/local/psa/version Path of the Plesk version file.
rekuest_cve_retry_after 300 Seconds to wait after a scanner error.
rekuest_cve_status_rate_limit 30 Failed-token attempts allowed per IP per minute.

Status endpoint for Zabbix

Transport URL
REST (primary) GET /wp-json/rekuest-cve/v1/status. With "Plain" permalinks: /?rest_route=/rekuest-cve/v1/status.
Alternative GET /wp-admin/admin-ajax.php?action=rekuest_cve_status
  • Authentication: X-Cve-Status-Token: <token> header. Authorization is not used because on Plesk with PHP under FastCGI/FPM it often does not reach PHP.
  • Forced refresh: ?refresh=1, ignored if the last attempt was less than 60 seconds ago.

Responses:

Code When
200 Authorized access. Always 200: a failed check is reported in the body (check_ok: 0, error).
401 Missing or wrong token.
403 IP not allowed (with REKUEST_CVE_ALLOWED_IPS).
404 Status token not configured. The route does not exist: the site responds as if the plugin were not installed.
429 More than 30 failed-token attempts in one minute from the same IP (or the same IPv6 /64 network). Sent with Retry-After.

Responses carry Cache-Control: no-store, private and X-Robots-Tag: noindex, and page-cache plugins are bypassed (DONOTCACHEPAGE).

Body ("schema 1", abridged):

{
  "schema": 1,
  "client": { "type": "wordpress", "name": "rekuest-cve-client", "version": "1.0.0" },
  "host": "www.example.com",
  "check_ok": 1,
  "error": null,
  "last_attempt_at": "2026-11-07T16:04:46Z",
  "last_success_at": "2026-11-07T16:04:46Z",
  "age_seconds": 120,
  "from_cache": true,
  "inventory": { "platform": 2, "composer": 0, "wordpress_plugins": 18, "wordpress_themes": 2 },
  "result": { "status": "vulnerable", "severity_level": 3, "max_cvss": 8.8, "packages": [ … ] }
}

result is the scanner response, forwarded as is: fields the scanner adds in the future reach Zabbix without a plugin update.

Security plugins and WAFs

  • Plugins that block the REST API for anonymous users: the plugin automatically exempts its own route from the rest_authentication_errors filter and from the "Disable REST API" plugin. If the REST API is still blocked (Wordfence, Solid Security or WAF rules), you have two options:

    • add rekuest-cve/v1 to the security plugin's exceptions;
    • point Zabbix at the admin-ajax.php URL.

    wp rekuest-cve status shows whether the endpoint responds as expected.

  • ModSecurity on Plesk: if Zabbix gets a 403 with an HTML page (rather than the plugin's {"message": …} JSON), the WAF blocked the request.

    1. Confirm with curl -i -H 'X-Cve-Status-Token: <token>' https://www.example.com/wp-json/rekuest-cve/v1/status: an HTML 403 comes from the WAF, a JSON 403 from the plugin.
    2. In Plesk, under Tools & Settings → Web Application Firewall (or in the domain's log), find the ID of the rule that blocked the request.
    3. Disable that rule for the domain, or point Zabbix at admin-ajax.php.

WP-CLI

Command Description
wp rekuest-cve check [--force] [--format=table\|json] Refreshes and shows the summary and the vulnerable packages. Exit code 0 on success, 1 on failure.
wp rekuest-cve inventory [--format=table\|json] Shows the inventory without calling the scanner.
wp rekuest-cve status Diagnostics: configuration (masked tokens), endpoints, Plesk file, REST API, result age, next cron event, updates.
wp rekuest-cve token Generates a status token and prints it, without saving it.

Plugin updates

Updates come from this repository's releases, with no third-party library and no token on the sites.

  1. Manifest: every 12 hours the plugin reads update.json from the latest release (releases/latest/download/update.json). The manifest holds the version, the ZIP URL, its SHA-384 hash and the signature.
  2. Notification: if the version is newer and the requirements are met, the update shows up in Dashboard → Updates like any other plugin. It follows WordPress's automatic update setting (or REKUEST_CVE_AUTO_UPDATE).
  3. Verification before installation: the plugin downloads the ZIP and verifies its hash and ed25519 signature. The signature covers slug, version and hash, so a package that has been tampered with, swapped, or passed off as another version is rejected. The public key ships with the plugin (Rekuest_Cve_Updater::PUBLIC_KEY); the private key exists only as a secret of the release workflow.
  4. No same-name plugin from wordpress.org: the plugin is never looked up on wordpress.org (Update URI header, and the plugin is removed from the update request), so a same-slug plugin published there cannot replace it.

For testing, another manifest can be used with the REKUEST_CVE_UPDATE_MANIFEST_URL constant. The signature is still required, so a different manifest cannot get unsigned code installed.

With WordPress < 5.2 and PHP < 7.2, libsodium is missing and the signature cannot be verified. In that case the plugin offers no updates and must be updated manually by uploading the new ZIP (Plesk WP Toolkit can do this in bulk).

Security

The plugin is designed not to add attack surface to sites that are frequent targets of automated scans.

  • Endpoint invisible when not configured: without a status token, the REST route and the AJAX action are not registered. Even when enabled, the endpoint is not listed in the /wp-json/ index, and OPTIONS requests and the namespace index respond as for a non-existent route.
  • Tokens:
    • compared in constant time (hash_equals);
    • the status token must be at least 32 characters (the generated one has 64, from random_bytes);
    • never shown on the page after generation, never included in responses or error messages.
  • No writes for anonymous requests: only attempts with a wrong token are counted, in a single size-bounded option. The database does not grow even under scans from thousands of IPs.
  • No CSRF bypass: the exemption for security plugins applies to the exact route only, and the request continues as anonymous.
  • Scanner connection:
    • https only, with certificate verification;
    • redirects are not followed, because they would carry the token elsewhere;
    • response capped at 1 MB.
  • Scanner data treated as external input:
    • non-scalar values are discarded, and all output is escaped;
    • links are allowed only towards the scanner itself.
  • Admin area: nonce and capability checks on every action (manage_network_options on multisite), safe redirects, PHP files not directly executable.
  • Release package: it contains no readme.txt, changelog or development files, so it does not reveal its version to fingerprinting scanners. Updates are signed (see above).

To report a vulnerability, read SECURITY.md. Do not open a public issue.

FAQ

How often is the scanner queried? At most once every 55 minutes (the cache TTL): Zabbix polls the site every hour and always finds a result due for renewal. An hourly WP-Cron event refreshes the result even without Zabbix. After an error, the next attempt waits 5 minutes, or the scanner's Retry-After if longer.

What if WP-Cron is disabled (DISABLE_WP_CRON)? The result is still refreshed on the first Zabbix poll after the TTL expires.

Can an unreachable scanner make the site look clean? No. check_ok is 0, error holds the reason and result keeps the last valid result.

What is left after uninstalling? Nothing: options (network options included), the lock, the attempt counter, transients and the cron event are all removed.

Development

rekuest-cve-client.php         header, constants, autoloader, bootstrap
uninstall.php                  complete data removal
includes/
  class-plugin.php             services, hooks, activation/deactivation, WP-Cron
  class-options.php            network options on multisite, autoload = no otherwise
  class-settings.php           wp-config.php constants / rekuest_cve_settings option
  class-inventory.php          core, plugins, themes, PHP, Plesk, Composer
  class-scanner-client.php     scanner call and error mapping
  class-state.php              persistent state and atomic lock (INSERT IGNORE)
  class-status-service.php     refresh algorithm and "schema 1" JSON
  class-status-endpoint.php    REST + admin-ajax, token, IPs, attempt limit
  class-updater.php            signed updates from GitHub releases
  class-admin-page.php         settings page
  class-dashboard-widget.php   "Vulnerabilità note" widget
  class-view.php               presentation helpers
  class-cli.php                WP-CLI commands
tests/                         PHPUnit with the WordPress test suite
bin/                           test setup, build, versioning, signing

Classes use the Rekuest_Cve_ prefix and a custom autoloader. The syntax is PHP 5.6-compatible (no scalar type hints, ??, void, arrow functions…), checked by PHPCompatibilityWP.

Design decisions

  • State in an option, not a transient: with an external object cache, transients can disappear, while the last valid result must survive.
  • Result stored as raw JSON and forwarded decoded as an object, so empty objects {} and new fields reach Zabbix unchanged.
  • Lock with INSERT IGNORE: add_option() uses INSERT … ON DUPLICATE KEY UPDATE and is not atomic under concurrent requests. The lock outlasts the call timeout.
  • REST authorization computed once per request: WordPress also calls the permission_callback for the Allow header.
  • Bedrock: besides ABSPATH/vendor, dirname(ABSPATH)/vendor and WP_CONTENT_DIR/../vendor, the plugin also looks in dirname(dirname(ABSPATH))/vendor.

Commands

You need PHP, Composer and MySQL/MariaDB for the test suite.

composer update                  # dev dependencies (no composer.lock: CI uses several PHP versions)
vendor/bin/phpcs                 # WordPress-Extra + PHPCompatibilityWP (PHP 5.6+)

bin/install-wp-tests.sh wp_tests root password 127.0.0.1 latest
vendor/bin/phpunit
WP_MULTISITE=1 vendor/bin/phpunit
  • install-wp-tests.sh accepts latest, trunk, X.Y.Z or X.Y; X.Y resolves to the latest patch of that branch, which includes the test suite fixes.
  • WordPress 5.0 supports PHPUnit up to 6.
  • composer.json ignores advisory PKSA-z3gr-8qht-p93v for old PHPUnit versions, which PHP 5.6/7.x need. PHPUnit never ends up in the ZIP.

At Rekuest, commands run in Rocker containers, e.g. rk run composer update or rk run php:8.4 vendor/bin/phpcs. The test database is rocker-mariadb-11, user root/rocker.

CI matrix:

PHP WordPress Mode
5.6 5.0 single site
7.4 5.9 single site
8.1 6.4 single site
8.3 latest single site
8.4 latest single site
8.4 latest multisite

Release

Every push to main produces a release, with no manual steps.

  1. Full CI (PHPCS and the test matrix). If it fails, nothing is released.

  2. Version calculation with bin/next-version.sh, from the latest vX.Y.Z tag and the commit messages in Conventional Commits format:

    Commit Version
    fix: …, chore: …, docs: …, … patch (1.4.2 → 1.4.3)
    feat: … minor (1.4.2 → 1.5.0)
    feat!: … or BREAKING CHANGE: in the body major (1.4.2 → 2.0.0)

    For a deliberate version jump, raise Version: in the plugin header: if it is higher than the calculated one, the header wins.

  3. ZIP: the version is written into the header and the constant in the working copy (no commit to main), then the ZIP is built without the files listed in .distignore.

  4. Signature: the ZIP is signed with ed25519 and update.json is written. The script refuses to sign if the key does not match the public key in the plugin.

  5. Publication: the vX.Y.Z tag and the GitHub release are created, with rekuest-cve-client.zip, update.json and notes generated from the commits.

With [skip release] in the commit message, that push does not produce a release.

Repository setup (one-time)

  • release environment: under Settings → Environments, create the release environment, restricted to the main branch, with the REKUEST_CVE_SIGNING_KEY secret (the base64 private key).
  • Branch protection on main: require pull requests and the CI checks.
  • Immutable releases: under Settings → General → Releases, so that published assets cannot be replaced.
  • Private vulnerability reporting: under Settings → Security, for SECURITY.md.

Rotating the signing key

  1. Generate a new key pair with php bin/signing-key.php /secure/path/key. The script prints the public key.
  2. Release a version that contains the new public key in Rekuest_Cve_Updater::PUBLIC_KEY, still signed with the old key.
  3. Once the sites have updated, replace the REKUEST_CVE_SIGNING_KEY secret.

If the private key is compromised, sites must be updated manually with a ZIP that contains the new key.

License

GPL-2.0-or-later, like WordPress. © Rekuest.