Security

ShipMCP's security model in one place: the threat model we address, the guarantees we make, and what we explicitly don't cover.

Threat model we address

  • Cross-tenant data access. A token issued for tenant A must never read or write tenant B's data, regardless of which slug it's pointed at or whether it leaks.
  • In-transit interception. Every request to shipmcp.io, www.shipmcp.io, and mcp.shipmcp.io is over TLS 1.3 with HSTS preload-eligible headers (max-age=31536000; includeSubDomains).
  • At-rest disclosure. A database operator with raw access to D1 or Neon must not see usable bearer tokens, passwords, or OAuth codes.
  • Token theft. A leaked token must be revocable. A leaked OAuth authorization code must be single-use and time-bounded. PKCE prevents code interception at the redirect step.
  • Agent overscope. An agent connected with the read-only mcp scope must not be able to mutate data even when the endpoint allows writes.

Tenant isolation

Every endpoint provisions a dedicated Neon Postgres project with its own connection string, its own storage quota, and its own audit boundary. This is physical isolation — not row-level security inside a shared database, not schema-level separation inside a shared cluster.

Token validation happens before any data path: the mcp-server resolves slug → endpoint → owning tenant on every request and rejects with 403 if the authenticated token's tenant doesn't match. See Tenant isolation for the full architectural picture.

Encryption

  • Transport: TLS 1.3 on every domain. HSTS preload-eligible.
  • At rest: Cloudflare R2 encrypts archived original-file uploads with AES-256. Neon encrypts Postgres at rest. Cloudflare D1 (control plane) is encrypted by Cloudflare's storage layer.
  • Bearer tokens: SHA-256 hex stored in tokens.token_hash. The raw token is shown to you exactly once at creation/rotation; we never log or persist it.
  • Passwords: bcrypt with cost factor 10.
  • Magic-link tokens: SHA-256-hashed, single-use, 15-minute TTL, hard-deleted within 24 hours after expiry.
  • OAuth authorization codes: SHA-256-hashed, single-use, 5-minute TTL, PKCE-S256 mandatory (we refuse plain).
  • Signed download URLs (resources/read large-file path, get_documents_file tool): HS256 JWT, 120-second TTL, tenant claim re-bound on every request, constant-time signature compare.
  • Signed upload URLs (request_upload_url tool): HS256 JWT, 300-second TTL, with a max claim baked in carrying the per-plan max-bytes-per-file. The PUT route enforces Content-Length precheck plus a post-upload size check that deletes the object if the actual body exceeded the cap.

Token + scope model

Tokens are tenant-scoped: one token covers every endpoint the granting tenant owns, and only those endpoints. Two scopes:

  • mcp — read-only. List, get, search, filter, count, cross-table joins, file resources. The default for every dashboard-issued token and for OAuth tokens where the user didn't grant write access.
  • mcp:write — read + queue-writes (insert/update/delete by primary key) + four built-in ingest tools. Only issued when the user explicitly tickedthe consent-screen checkbox at shipmcp.io/oauth/authorize.

The OAuth consent screen is the trust gate. ShipMCP doesn't broker between agents and your data — the human user explicitly authorizes each connected app, with the write-scope upgrade as a separate, prominently-warned checkbox. Per-app revocation is one click on the dashboard's API tokens panel; the oauth_client_id column lets you find and revoke a specific connection without affecting others.

Audit trail

Every significant event lands in the audit_log table:

  • endpoint_provisioned, endpoint_data_appended, endpoint_provision_failed, endpoint_deleted, account_deleted
  • mcp_write — every successful agent-initiated insert/update/delete with the row id, tool name, and oauth_client_id
  • tools_rebuilt — every manifest regeneration triggered by the writes toggle or the manual Rebuild button

Audit log is retained for 12 months. Operational logs (Cloudflare Workers console.log) are retained for 30 days.

What we explicitly don't cover

We're transparent about what ShipMCP isn't suitable for:

  • No FedRAMP, CMMC, or IL4-IL5 authorization. ShipMCP is hosted on Cloudflare's commercial cloud; we have no plans to certify under federal frameworks.
  • Not for ITAR / CUI / classified data. Our Terms of Service explicitly forbid uploading regulated content without a separate written agreement covering it.
  • Not designed for adversarial multi-tenant. We protect tenants from each other at the data layer, but we trust authenticated tenants not to attack the platform itself. A tenant who tries to compromise our infrastructure (DoS, prompt injection at our control plane, attempts to escape their boundary) violates the Acceptable Use clause and we'll terminate.
  • Not HIPAA-ready. No BAA available; do not upload PHI.
  • No SOC 2 yet. Type 1 is on the roadmap; ask about it on enterprise inquiries.

Reporting a vulnerability

Email matt@mrdula.solutions. We aim to acknowledge within 48 hours and triage within 5 business days. We don't run a paid bug bounty today — happy to credit researchers in our changelog, and we'll reach out about coordinated disclosure for anything that affects active customers.

Please don't test against tenants other than your own. Provisioning a free endpoint and attacking it is fine; provisioning a free endpoint to attack our infrastructure or another tenant is not.