WP Manifestindependent plugin directory
manifest / integrations / clockwork-companion

Clockwork Companion

Companion mu-plugin for Clockwork Control — signed REST endpoints for fleet-wide WordPress maintenance

by Clockwork Web Dev, LLC · github.com/clockwork-web-dev-llc/clockwork-companion

0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/clockwork-web-dev-llc/clockwork-companion/archive/refs/heads/main.zip

Readme

Clockwork Companion

WordPress mu-plugin that exposes signed REST endpoints under /wp-json/clockwork/v1/ so the Clockwork monitoring app can drive fleet-wide WordPress maintenance tasks: contact-form testing today, plugin/theme/core update orchestration, security scans, and white-label client reporting in future releases.

This plugin is deployed by Clockwork, not installed by hand. See clockwork:install-companion in the Clockwork app.

Architecture

  • Loader: clockwork-companion.php (mu-plugin entry; defines version, registers PSR-4 autoloader, boots Plugin)
  • Auth: HMAC-SHA256 signature with 5-minute replay window. Per-site secret in wp_options.clockwork_companion_secret. See src/Auth/HmacVerifier.php.
  • Routes: src/Rest/*/health, /detect, /test-contact-form (more added per release)
  • Form-test strategies: src/ContactForm/Strategies/* — one per supported form plugin (CF7, WPForms, Gravity)

Signature scheme

Clients sign requests with two headers:

X-Clockwork-Timestamp: <unix seconds>
X-Clockwork-Signature: <hex hmac-sha256 of payload>

Where payload is:

METHOD\n/wp-json/<route>\n<timestamp>\n<body>

Body is the empty string for GET. The replay window is 300 seconds; requests outside it are rejected even with valid signatures.

Capabilities advertised by /health

{
  "ok": true,
  "version": "1.0.0",
  "capabilities": ["contact-form-test"],
  "wp_version": "6.x",
  "php_version": "8.x"
}

Clockwork stores the latest snapshot per site so it always knows which features are available without re-probing.

Configuration constants

Both constants are defined in wp-config.php. Both are optional — Companion runs with safe defaults if neither is set.

CLOCKWORK_COMPANION_SECRET

define('CLOCKWORK_COMPANION_SECRET', 'hex-encoded-32-bytes');

When defined, takes precedence over the wp_options.clockwork_companion_secret row. Recommended for security-sensitive sites — keeps the HMAC secret out of database backups and out of the SQL-injection blast radius of any other plugin. Rotation is manual: edit wp-config.php and re-run php artisan clockwork:rotate-companion-secret <site> so Clockwork picks up the new value.

CLOCKWORK_COMPANION_TRUST_PROXY (1.16.9+)

define('CLOCKWORK_COMPANION_TRUST_PROXY', true);

Tells HmacVerifier::clientIp() to read the real client IP from CF-Connecting-IP (preferred) or X-Forwarded-For (leftmost entry) instead of REMOTE_ADDR. Required on Cloudflare-fronted (or any reverse-proxied) sites — without it, every request appears to come from the proxy edge IP, which:

  1. Neutralises the per-IP rate limiter (one noisy probe locks out the entire planet, since every request shares one bucket).
  2. Pollutes the auth-failure audit table with edge IPs instead of attacker IPs.

Opt-in by design. Trusting forwarded headers without an actual proxy in front lets a direct attacker spoof their bucket key. Only define this constant when a trusted reverse proxy (Cloudflare, your load balancer, etc.) is verifiably in front of the WordPress origin.

For Clockwork-managed care-plan sites, the clockwork:ensure-companion-trust-proxy command idempotently injects this constant fleet-wide and runs nightly to catch newly-eligible sites.

Local development

ln -s "$PWD" /path/to/wp-content/mu-plugins/clockwork-companion
ln -s "$PWD/clockwork-companion.php" /path/to/wp-content/mu-plugins/clockwork-companion.php

The two symlinks let WordPress load the plugin while you edit source in this repo. Then exercise endpoints with a small signing helper (see bin/sign.php once added).

Read the full README on GitHub →