Bridgistic connects AI tooling to production websites, so the security posture is deliberately strict. This document describes the mechanisms and the reasoning.
The bridge defends against:
- Credential theft in transit — secrets never travel on the wire.
- Replay of captured requests — timestamp window + single-use nonces.
- Over-privileged automation — least-privilege scopes per key.
- AI mistakes on destructive operations — dry-run, approvals, automatic snapshots, rollback.
- Database compromise of the WordPress site — secrets encrypted at rest.
- Unaccountable changes — tamper-evident audit log.
It does not replace transport security (use HTTPS) or WordPress core hardening.
Every request from the MCP server carries four headers:
X-Bridgistic-Key public key id
X-Bridgistic-Timestamp unix seconds
X-Bridgistic-Nonce random per-request id
X-Bridgistic-Signature hex HMAC-SHA256
The signature covers METHOD \n PATH \n TIMESTAMP \n NONCE \n sha256(body). The plugin recomputes it with the key's secret and compares in constant time (hash_equals). Rejections:
| Condition | Error |
|---|---|
| Timestamp outside ±300 s | bridgistic_auth_stale |
| Unknown/disabled key | bridgistic_auth_key |
| Source IP not in key's allowlist | bridgistic_auth_ip |
| Signature mismatch | bridgistic_auth_signature |
| Nonce already used | bridgistic_auth_replay |
This is strictly stronger than a bearer Application Password: a captured request cannot be replayed and never contains the secret.
A common question. Password-style hashing is impossible here: HMAC verification requires the live secret on the server to recompute the signature. So the plugin:
- stores secrets encrypted at rest — libsodium
secretboxwhen available, otherwise AES-256-GCM (both authenticated encryption); - derives the encryption key from a per-site pepper + WordPress auth salts, or from
BRIDGISTIC_ENC_KEYinwp-config.phpif you define it (recommended — it moves key material out of the database entirely); - shows the plaintext secret exactly once at creation/rotation, in the response to the admin who created it, and never again;
- never writes secrets to logs, exports (without explicit opt-in inside a 2-minute post-creation window), page source, or debug reports.
Every key carries an explicit scope list (19 scopes, e.g. posts:read, options:write, php:execute). Every REST controller checks the scope before the operation; denials are audit-logged. The admin UI presets map to increasing trust: Read-only → Content Manager → Safe Admin (approval on all writes) → Developer Mode (everything, approval on destructive ops, loudly labeled).
Destructive operations pass through a pipeline:
- Dry-run available for previewing effects (rolled-back transaction for SQL).
- Approval — if the key requires it, the operation is queued; a human decides in Bridgistic → Approvals; the agent retries with the approval id.
- Automatic snapshot of the target (post, option, tables, file) before the write.
- Execute, with the result audit-logged.
Rollback is one call (bridgistic_snapshot_restore) or one click in WP Admin.
bridgistic_execute_php, bridgistic_db_query (writes), and bridgistic_fs_write are the highest-risk surface:
- gated behind
php:execute/db:write/fs:writescopes — no preset short of Developer Mode grants them; - destructive SQL auto-snapshots affected tables; PHP writes are confined to a web-execution-blocked sandbox directory (
.htaccessdeny +index.php), never plugins/themes/mu-plugins; - combined with
require_approval, every such operation waits for a human.
Recommendation: never mint a Developer Mode key for a site you don't fully control.
Every operation records: key id, action, status, source IP, timestamp, a SHA-256 hash of the parameters (not the raw values), and a short human summary. Retention 90 days (pruned daily). The Logs screen and health debug report expose no secrets by design.
- Every screen and handler requires
manage_options. - Every state-changing request carries a WordPress nonce (
check_admin_referer/check_ajax_referer). - All input sanitized (
sanitize_text_field,sanitize_key,absint, …), all output escaped (esc_html,esc_attr,esc_url). - Admin assets load only on Bridgistic screens.
- stdio transport by default (local, no listening port).
- The optional HTTP transport binds to
127.0.0.1and refuses non-loopback requests unless you explicitly setBRIDGISTIC_HTTP_TOKEN(constant-time bearer check). Do not expose it publicly; the remote connector is a SaaS feature for a reason. - Logs go to stderr only and never include secrets.
connections.jsonshould be readable only by your user.
- HTTPS on the site
-
BRIDGISTIC_ENC_KEYdefined inwp-config.php - Read-only or Content Manager keys for daily use
-
require approvalon any writing key for a production site - IP allowlist on keys when your machine has a stable address
- Real system cron (not WP-Cron) if you use schedules
- Health Check score reviewed after any hosting change
Email support@wordpressistic.com with details. Please do not open public issues for security reports; we'll credit you in the changelog once fixed.