Skip to main content

Overview

GetMCP signs every call log entry with an HMAC-SHA256 signature at the moment it is written. If any stored field is later modified — whether accidentally or deliberately — the signature no longer matches, and the verifier flags the row as tampered. This makes the call log effectively append-only: entries can be deleted (and that deletion is itself detectable by gaps), but they cannot be silently edited.

How Signing Works

When a call log row is inserted into wp_getmcp_call_logs, GetMCP:
  1. Assembles a canonical string from the row’s key fields in a fixed order.
  2. Derives a signing key from WordPress’s AUTH_KEY constant using HKDF with the domain string getmcp-log-signing-v1.
  3. Computes HMAC-SHA256(canonical_string, signing_key).
  4. Stores the 64-character hex digest in the signature column of the same row.
The signing key is zeroed from memory after use (sodium_memzero) when the PHP sodium extension is available.

Canonical Fields

The canonical string includes the following fields (joined with |):
FieldDescription
idAuto-increment row ID
server_idServer that received the call
tool_idTool that was called
methodMCP method (tools/call)
argumentsSanitized input arguments (JSON)
response_statussuccess or error
response_time_msRound-trip time in milliseconds
status_codeUpstream HTTP status code
client_typeDetected AI client
client_ipCaller IP address
user_agentCaller user-agent string
error_messageError text (if applicable)
response_dataFull response (if Log Response Data is on)
created_atUTC timestamp
Fields not included in the canonical form (so adding them later doesn’t break old signatures):
  • upstream_bytes / delivered_bytes
  • replay_data

Verifying the Audit Log

Logs page — More actions kebab menu with Verify integrity item Go to GetMCP → Logs, open the More actions () kebab menu in the toolbar, and choose Verify integrity. GetMCP will:
  1. Fetch all rows matching your current filter (date range, server, status).
  2. Recompute the HMAC for each row.
  3. Compare it to the stored signature.
  4. Return a summary report.

Verification result states

Audit log verification summary showing Verified, Tampered, and Unsigned row counts
StateMeaning
VerifiedSignature matches — row is intact
TamperedSignature does not match — field values were modified after write
Unsigned (legacy)Row has no signature — written before audit log was introduced (DB < 1.12)
Unsigned (signer unavailable)AUTH_KEY is missing or too short; signing was skipped at write time
Rows marked Unsigned (legacy) are not evidence of tampering — they simply pre-date the feature. Only rows with a stored signature can be verified.

Requirements

RequirementDetail
AUTH_KEYMust be defined in wp-config.php and at least 16 characters — standard on all healthy WordPress installs
hash_hmacPHP core since 5.1.2 — always available
sodium_memzeroOptional — used to zero the key from memory after signing; signing still works without it
DB version ≥ 1.12The signature column is added by the 1.12 migration

Security Considerations

Key rotation

The signing key is derived from AUTH_KEY. If you rotate AUTH_KEY (e.g. after a security incident), all existing signatures will fail verification — they were signed with the old key. This is expected behaviour. After rotation, treat all pre-rotation rows as unsigned legacy rows.

What tampering looks like

A tampered row has a stored signature value that no longer matches the HMAC of its current field values. The verifier flags it as Tampered and shows the row ID so you can investigate. Common accidental causes of failed verification:
  • Directly editing rows in phpMyAdmin or a MySQL client
  • A database migration that modified existing rows
  • Restoring a partial DB backup that mixed rows from two different AUTH_KEY periods

What the audit log does not prevent

The audit log detects tampering — it does not prevent it. A user with direct MySQL access can delete rows or the entire table. Use database-level access controls and regular off-site backups for defence-in-depth.

Daily Automated Verification

A WordPress cron job (getmcp_daily_audit_verify) runs once per day and re-verifies every signed row from the previous calendar day (UTC). If any row’s signature does not match, GetMCP sends an alert via wp_mail() to the notification email configured at GetMCP → Settings → Notifications (falls back to the WordPress admin_email if unset). No email is sent on a clean run. The same scan can be triggered ad-hoc against any filter range from GetMCP → Logs → More actions → Verify integrity, or via POST /wp-json/getmcp/v1/analytics/audit/verify for scripted full-history sweeps. A getmcp_audit_tampering_detected action also fires with the list of tampered row IDs, so you can hook into it from a custom plugin (e.g. to forward to a SIEM).