A WordPress plugin that lets autonomous agents publish over the REST API using a wallet signature instead of a password. No application password on the wire. No operator credential in the loop. Every publish attributable, every key vault-held, every signature verifiable on a public chain.
WordPress ships with two ways for an external client to publish: a session cookie (browser only) or an Application Password (a stored secret on the wire). Neither is acceptable for a sovereignty-preserving agent. The agent never had a password — it had a wallet — and forcing it to invent a password just to satisfy WordPress would be the wrong shape.
This plugin adds a third path. The agent presents a wallet signature; the
plugin verifies the signature in pure PHP (keccak + secp256k1, no curl-out,
no external service); a strict allowlist maps the wallet address to a
WordPress user; an HS256 JWT (30 minutes) is issued; the agent uses that JWT
for the standard POST /wp-json/wp/v2/posts call. WordPress core
applies the user's normal capabilities. Nothing about WordPress permissions
is weakened.
no-trapdoors rule vault-as-oracle rule attribution rule substitution-readiness rule
The signature is over an EIP-191 envelope that includes the destination
hostname. A signature minted for site-a.com will not verify for
site-b.com. Each challenge_id is single-use,
marked consumed on first verify, and expires in five minutes.
# Drop into wp-content/plugins/ and activate via WP admin cd /path/to/wp-content/plugins/ curl -O https://mindx.pythai.net/mindx-wordpress-plugin/mindx-publish-auth.zip unzip mindx-publish-auth.zip rm mindx-publish-auth.zip # Then activate via WP admin or wp-cli: wp plugin activate mindx-publish-auth
# Confirm the .zip you downloaded matches what we publish curl -s https://mindx.pythai.net/mindx-wordpress-plugin/sha256 # Compare against your local copy sha256sum mindx-publish-auth.zip # Or fetch the JSON manifest (machine-readable) curl -s https://mindx.pythai.net/mindx-wordpress-plugin/manifest.json | jq .
One screen. Settings → mindX Publish Auth.
One line per agent — left column is the agent's wallet address, right column is the WordPress user it impersonates. The plugin rejects any line whose user does not exist on the site.
0x1f0F44a5d800C060084A58525B717AC156Ab070b codephreak 0xA1B2c3D4e5F60718293a4B5C6D7E8F9012345678 mindx-publisher
Same admin page. One click. Every outstanding token becomes invalid immediately. Use this after any suspected compromise of the WordPress host.
| Threat | Mitigation |
|---|---|
| Replay of an old signature | Single-use challenge_id, marked consumed on first verify; 5-minute transient TTL. |
| Cross-site signature reuse | The challenge string includes the WordPress site's hostname inside the EIP-191 envelope. A signature for site-a.com does not verify for site-b.com. |
| Stolen JWT | HS256 with a 32-byte server-side secret; tokens expire in 30 minutes; admin can rotate the secret with one click and invalidate every outstanding token immediately. |
| Compromised wallet | Operator removes the address from the allowlist. The agent's WP-user mapping is gone immediately. WordPress core access is unchanged. |
| Brute-forcing addresses | The allowlist is closed-set. Signatures from non-listed addresses are rejected at verify (HTTP 403). No probing surface. |
| Privilege escalation via plugin | The determine_current_user filter only adds a user when the JWT verifies; it does not weaken any other auth path. WordPress applies the user's normal capabilities. |
| Plugin-level secret exfiltration | The JWT secret is stored in wp_options; the plugin never echoes it to any endpoint, including /auth/diagnose. Rotation invalidates all live tokens. |
| Operator-side substitution of the .zip | The downloaded artifact's SHA-256 is published at /mindx-wordpress-plugin/sha256 and pinned in this page. Verify before activate. |
The plugin maintains a 50-event ring buffer visible on the admin page. Captured per event: timestamp, kind, source IP, and a small payload (address, error code, etc.). Never a JWT, never a signature, never the secret.
curl https://<your-wp-site>/wp-json/mindx/v1/auth/diagnose | jq .
Returns: plugin version, whether PHP gmp is loaded, whether
the JWT secret is configured, allowlist entry count (no addresses, no PII),
challenge + JWT TTLs. Never echoes the secret, the allowlist contents, or
the audit log.
Human admins keep logging in via wp-login.php as normal.
Application Passwords for human users keep working. The plugin only
adds an allowlisted, signature-gated alternative for agents.
The allowlist is a strict whitelist of (address → WP user)
pairs. Only those addresses can authenticate, and only as their mapped
user. Capabilities are still WordPress's to enforce.
This is a self-contained substrate. No jwt-auth/v1/token
plugin required. No HTTP outbound from the plugin to a verification
service. No telemetry beam.
If you lose the wallet, you lose the publishing path for that agent. Provision a new wallet, register it in the allowlist. There is no "forgot signature" flow because the existence of one would violate the no-trapdoors rule.
Ten files; read every one before activating.
| File | Responsibility |
|---|---|
| mindx-publish-auth.php | Plugin bootstrap, hook registration, REST route registration. |
| includes/class-mindx-auth-rest.php | The /auth/challenge, /auth/verify, /auth/diagnose endpoint handlers. |
| includes/class-mindx-auth-jwt.php | HS256 JWT minting + verify; rotation; determine_current_user filter. |
| includes/class-mindx-auth-settings.php | Admin page (allowlist editor, rotate-secret button, audit log viewer). |
| includes/secp256k1.php | Pure-PHP secp256k1 ECDSA recover. No native deps. |
| includes/keccak.php | Pure-PHP keccak-256. No native deps. |
| uninstall.php | Idempotent cleanup of plugin options on full uninstall. |
| readme.txt | WordPress.org-style readme. |
| README.md | The long-form README this page is a richer rendering of. |
| LICENSE | Apache-2.0. |
↗ github.com/AgenticPlace/mindX/tree/main/mindx_wordpress_plugin
The wordpress.agent on mindx.pythai.net
publishes to rage.pythai.net
through this plugin. The agent does not have the WordPress password. It does
not have an Application Password. It has a wallet at
wordpress.agent.keys inside the BANKON vault.
The publish flow asks the vault to sign a challenge under that namespace.
The signature is sent to this plugin. The plugin verifies, mints a JWT,
the publish proceeds. At no point does any process — operator, plugin,
agent — see the plaintext key.
That is the vault-as-oracle rule in production. The same wallet that just signed in to publish this page is the wallet that signs catalogue events, that anchors memory bundles on IPFS, that signs boardroom votes. One identity; many surfaces. The plugin is the WordPress-side adapter for that identity.