Skip to content

Security: RegionallyFamous/pressocampus

Security

docs/security.md

Security

Pressocampus handles personal data — your thoughts, preferences, and decisions. Security is not an afterthought.


Authentication model

Pressocampus uses OAuth 2.1 with PKCE as its sole authentication method. There are no API keys, application passwords, or unauthenticated endpoints.

Why OAuth 2.1?

  • Standard — the same protocol used by banks, Google, and GitHub. Every modern AI client knows how to use it.
  • No credentials in config files — you never put your WordPress password anywhere near your AI client.
  • Granular revocation — you can disconnect a specific client without affecting others or changing your password.
  • Short-lived access tokens — tokens expire; refresh tokens are rotated on use.

Why PKCE?

PKCE (Proof Key for Code Exchange) prevents authorization code interception attacks. Even if an attacker intercepts the authorization code during the OAuth flow, they cannot exchange it for tokens without the code verifier that only the legitimate client has.

This is especially important for AI clients that run locally, where redirect URIs are less controllable.


OAuth flow

AI Client                           Pressocampus                    You
    │                                     │                            │
    │── POST /oauth/register ────────────>│                            │
    │<─ client_id, client_secret ─────────│                            │
    │                                     │                            │
    │── GET /oauth/authorize ─────────────>│                           │
    │                                     │── Consent screen ─────────>│
    │                                     │<─ "Allow" ─────────────────│
    │<─ authorization_code ───────────────│                            │
    │                                     │                            │
    │── POST /oauth/token ────────────────>│                           │
    │<─ access_token, refresh_token ──────│                            │
    │                                     │                            │
    │── POST /mcp (Bearer token) ─────────>│                           │
    │<─ MCP response ─────────────────────│                            │

Dynamic client registration

AI clients register themselves automatically via POST /pressocampus/v1/oauth/register. No manual app registration required.

Registered client credentials:

  • Client ID — a 32-character cryptographically secure random hex string (bin2hex(random_bytes(16))), not a predictable time-based value.
  • Client secret — generated with wp_generate_password(40) and stored as a bcrypt hash (PASSWORD_BCRYPT). The plaintext secret is returned to the client once at registration and never stored. Existing clients registered before v1.0.25 with plaintext secrets continue to work.

Access tokens

  • JWT format, RSA-signed
  • Expire after 8 hours (configurable)
  • Include the WordPress user ID as the subject

Refresh tokens

  • Opaque, stored hashed in the database
  • Rotated on every use — the old refresh token is immediately invalidated when a new one is issued
  • Expire after 30 days of inactivity

Consent screen

The consent screen tells users exactly what access is being granted:

[Client Name] is requesting permission to read, write, and delete memories on your Pressocampus brain.

There is a single scope: pressocampus:memory. There is no read-only mode — an authorized client has full access to your memories.


RSA key pair

On activation, Pressocampus generates a 2048-bit RSA key pair:

  • Private key — stored as a PEM string in wp_options (key: pressocampus_rsa_private_key). Protect your wp_options table and WordPress filesystem accordingly.
  • Public key — stored in plaintext, exposed in the OAuth authorization server metadata. Used by clients to verify token signatures.

The key pair is regenerated if the private key is ever deleted. You can see the public key fingerprint in Settings → Advanced.

The RSA key is not included in brain exports. Exports contain only memory content and metadata.


Per-user data isolation

Every memory is owned by a specific WordPress user (post_author). The OAuth token identifies which user is authenticated, and all queries are automatically scoped to that user.

It is not possible for an AI client authorized as User A to read, write, or delete User B's memories. This is enforced at the database query level, not just at the API level.


Rate limiting

To prevent abuse and accidental runaway automation:

Operation Limit
Read operations 60 per minute per user
Write operations 30 per minute per user

The initialize handshake also counts against the read limit. This prevents a scenario where an attacker rapidly reconnects to trigger unbounded soul-creation database work without being throttled.

When a limit is exceeded for a tool call, the tool returns a rate_limit_exceeded error response (the MCP layer returns an isError: true content block). For resources/list and resources/read, a JSON-RPC error is returned. In both cases the HTTP status is 400.

Limits are tracked per access token using WordPress object caching (falls back to transients if no external object cache is present). An authenticated session with no token ID is denied rather than allowed to bypass throttling.


CORS policy

Pressocampus uses a CORS allowlist on the MCP endpoint. CORS headers (Access-Control-Allow-Origin, Access-Control-Allow-Credentials) are only sent if the requesting origin matches:

  1. The site's own origin (always allowed)
  2. An origin you've explicitly added in Settings → Advanced → CORS

Requests without an Origin header (typical for desktop AI clients like Claude Desktop and Cursor) are unaffected by CORS — they bypass the origin check entirely.

Add browser-based AI client origins to the CORS allowlist if they send an Origin header with their requests.


HTTPS requirement

OAuth 2.1 requires HTTPS. Pressocampus will not issue tokens over HTTP in production. The consent screen redirects to HTTPS if an HTTP request is received.

In local development environments (localhost), HTTP is allowed for testing.


Input sanitization

All memory content is sanitized before storage:

  • Content is stored raw (unsanitized) and escaped on output — following WordPress best practices
  • MIME type is validated against an allowlist
  • URI uniqueness is enforced at the database level
  • Content size is validated against the configured maximum

Threat model

What Pressocampus protects against

  • Unauthorized access — no token, no access
  • Cross-user data access — per-user query scoping
  • Token interception — PKCE in the authorization flow
  • Concurrent write conflicts — ETag-based optimistic locking on memories; MySQL advisory locks (GET_LOCK) on soul creation and index rebuilds
  • Runaway AI writes — rate limiting
  • Accidental deletion — soul/index are protected, deletion requires explicit context
  • History audit trail — every write operation is logged with agent name, timestamp, and context so you can review exactly what your AI has been doing

What Pressocampus does not protect against

  • A compromised WordPress admin account — if someone has admin access to your WordPress site, they have access to your memories. Secure your WordPress admin.
  • A compromised AI client — if your AI client is compromised, the attacker has your OAuth tokens. Revoke from Settings → Advanced → Connected Apps.
  • The WordPress host — your memories live on your server. Secure your server.
  • An AI that goes rogue — Pressocampus logs everything in History. Review it periodically.

Responsible disclosure

If you find a security vulnerability in Pressocampus, please report it privately by opening a GitHub Security Advisory rather than a public issue.

There aren't any published security advisories