Skip to content

A CMS / ops agent on WordPress and WooCommerce

The situation

A shop or publisher lets an agent run the site through WordPress's Abilities API: tidy posts, update products, install the plugin a task needs. The reeflex-gate plugin intercepts every ability call inside WordPress and asks core before it runs.

What goes wrong without a gate

A CMS admin account can do everything, and an agent with one will. "Clean up old drafts" becomes a bulk delete past the trash; "add a contact form" becomes a plugin installed from the directory onto production, running code with full database access. Each call is a normal admin action.

Three representative actions

Each envelope is what the plugin's Reeflex_Normalizer::normalize() emits for the ability and input shown, trimmed of transport fields.

Deleting one post. Posts go to the trash, so the normaliser prices this recoverable/single. Allowed, and counted toward the session's deletions budget (20 by default).

Verdict: allow · rule reeflex.policy/default_allow

{
  "agent":     { "id": "agent:wordpress", "on_behalf_of": "user:7", "session_id": "wpsess:4be1" },
  "action":    { "namespace": "wordpress", "verb": "delete", "ability": "core/delete-post" },
  "target":    { "kind": "post", "ref": "post:7", "environment": "production" },
  "magnitude": { "count": 1 },
  "axes":      { "reversibility": "recoverable", "blast_radius": "single", "externality": "internal" },
  "approval":  { "present": false }
}

Deleting 25 posts in one call. Twenty or more ids is broad, and a bulk delete is priced irreversible. In production that is R2: held.

Verdict: require_approval · rule reeflex.policy/irreversible_broad_prod

{
  "agent":     { "id": "agent:wordpress", "on_behalf_of": "user:7", "session_id": "wpsess:4be1" },
  "action":    { "namespace": "wordpress", "verb": "delete", "ability": "core/delete-posts" },
  "target":    { "kind": "resource", "ref": null, "environment": "production" },
  "magnitude": { "count": 25 },
  "axes":      { "reversibility": "irreversible", "blast_radius": "broad", "externality": "internal" },
  "approval":  { "present": false }
}

Installing a plugin. The axes are mild — one plugin, recoverable — and no axis rule fires. R7 does: installing a plugin changes what code runs in production, so it is held under the authority rule.

Verdict: require_approval · rule reeflex.policy/authority_change_prod

{
  "agent":     { "id": "agent:wordpress", "on_behalf_of": "user:7", "session_id": "wpsess:4be1" },
  "action":    { "namespace": "wordpress", "verb": "create", "ability": "core/install-plugin" },
  "target":    { "kind": "plugin", "ref": null, "environment": "production" },
  "magnitude": { "count": 1 },
  "axes":      { "reversibility": "recoverable", "blast_radius": "single", "externality": "internal" },
  "approval":  { "present": false }
}

How to deploy it

Install the reeflex-gate plugin (Plugins → Add New → Upload Plugin), or as a must-use plugin with one line in wp-config.php:

define( 'REEFLEX_CORE_URL', 'https://your-reeflex-core-host' );

The plugin enforces by default. The other line that matters is REEFLEX_ENV: it defaults to production, which arms R2 and R7; set it on a staging site, not on a production one to quiet it. Full guide: WooCommerce adapter.

What Reeflex does NOT do here

  • It does not judge content. A post rewritten into nonsense, or a price set to €0.01 on one product, is a single reversible update and is allowed.
  • It does not review the plugin. The hold asks a person whether this plugin may be installed; it does not scan the plugin's code.
  • It does not see writes that bypass the Abilities API — direct SQL from another plugin, WP-CLI, a file uploaded over SFTP.
  • One post at a time stays allowed until the session's deletions budget is spent: the 21st single delete in a session is held, the first 20 are not.